diff options
Diffstat (limited to 'tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html')
-rw-r--r-- | tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html new file mode 100644 index 0000000000..34c2d3dc3d --- /dev/null +++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-scene.html @@ -0,0 +1,475 @@ +<link rel="import" href="../../bower_components/polymer/polymer.html"> +<link rel="import" href="tf-graph-style.html"> +<link rel="import" href="tf-graph-minimap.html"> +<script src="../tf-graph-common/lib/layout.js"></script> +<script src="../../bower_components/dagre/dist/dagre.core.js"></script> +<!-- + A module that takes graph-hierarchy as input and produce + a svg dom using dagre and d3. +--> +<dom-module id="tf-graph-scene"> +<template> +<style include="tf-graph-style"> + :host { + font-size: 20px; + } + .titleContainer { + position: relative; + } + .title { + position: absolute; + } + .auxTitle { + position: absolute; + } + #minimap { + position: absolute; + right: 20px; + bottom: 20px; + } +</style> +<div class="titleContainer"> + <div id="title" class="title">Main Graph</div> + <div id="auxTitle" class="auxTitle">Auxiliary nodes</div> +</div> +<svg id="svg"> + <defs> + <!-- Arrow head for edge paths. --> + <marker id="arrowhead" markerWidth="10" markerHeight="10" + refX="9" refY="5" orient="auto"> + <path d="M 0,0 L 10,5 L 0,10 C 3,7 3,3 0,0"/> + </marker> + <marker id="ref-arrowhead" markerWidth="10" markerHeight="10" + refX="1" refY="5" orient="auto"> + <path d="M 10,0 L 0,5 L 10,10 C 7,7 7,3 10,0"/> + </marker> + <!-- Arrow head for annotation edge paths. --> + <marker id="annotation-arrowhead" markerWidth="5" markerHeight="5" + refX="5" refY="2.5" orient="auto"> + <path d="M 0,0 L 5,2.5 L 0,5 L 0,0"/> + </marker> + <marker id="ref-annotation-arrowhead" markerWidth="5" markerHeight="5" + refX="0" refY="2.5" orient="auto"> + <path d="M 5,0 L 0,2.5 L 5,5 L 5,0"/> + </marker> + <!-- Template for an Op node ellipse. --> + <ellipse id="op-node-stamp" + rx="7.5" ry="3" stroke="inherit" fill="inherit" /> + <!-- Template for an Op node annotation ellipse (smaller). --> + <ellipse id="op-node-annotation-stamp" + rx="5" ry="2" stroke="inherit" fill="inherit" /> + <!-- Vertically stacked series of Op nodes when unexpanded. --> + <g id="op-series-vertical-stamp"> + <use xlink:href="#op-node-stamp" x="8" y="9" /> + <use xlink:href="#op-node-stamp" x="8" y="6" /> + <use xlink:href="#op-node-stamp" x="8" y="3" /> + </g> + <!-- Horizontally stacked series of Op nodes when unexpanded. --> + <g id="op-series-horizontal-stamp"> + <use xlink:href="#op-node-stamp" x="16" y="4" /> + <use xlink:href="#op-node-stamp" x="12" y="4" /> + <use xlink:href="#op-node-stamp" x="8" y="4" /> + </g> + <!-- Horizontally stacked series of Op nodes for annotation. --> + <g id="op-series-annotation-stamp"> + <use xlink:href="#op-node-annotation-stamp" x="9" y="2" /> + <use xlink:href="#op-node-annotation-stamp" x="7" y="2" /> + <use xlink:href="#op-node-annotation-stamp" x="5" y="2" /> + </g> + <!-- + Where the linearGradient for each node is stored. Used when coloring + by proportions of devices. + --> + <g id="linearGradients"></g> + </defs> + <!-- Make a large rectangle that fills the svg space so that + zoom events get captured on safari --> + <rect fill="white" width="10000" height="10000"></rect> + <g id="root"></g> +</svg> +<tf-graph-minimap id="minimap"></tf-graph-minimap> +</template> +</dom-module> +<script> +Polymer({ + is: 'tf-graph-scene', + properties: { + graphHierarchy: Object, + name: String, + colorBy: { + type: String, + observer: '_colorByChanged' + }, + /** @type {d3_zoom} d3 zoom object */ + _zoom: Object, + highlightedNode: { + type: String, + observer: '_highlightedNodeChanged' + }, + selectedNode: { + type: String, + observer: '_selectedNodeChanged' + }, + /** Keeps track of if the graph has been zoomed/panned since loading */ + _zoomed: { + type: Boolean, + observer: '_onZoomChanged', + value: false + }, + /** Keeps track of the starting coordinates of a graph zoom/pan */ + _zoomStartCoords: { + type: Array, + value: null + }, + /** Keeps track of the current coordinates of a graph zoom/pan */ + _zoomCoords: { + type: Array, + value: null + }, + /** Maximum distance of a zoom event for it to be interpreted as a click */ + _maxZoomDistanceForClick: { + type: Number, + value: 20 + }, + /** + * @type {d3.scale.ordinal} + * Scale mapping from template name to a number between 0 and N-1 + * where N is the number of different template names. + */ + templateIndex: Object, + /** + * @type {tf.scene.Minimap} + * A minimap object to notify for zoom events. + */ + minimap: Object, + /* + * Dictionary for easily stylizing nodes when state changes. + * _nodeGroupIndex[nodeName] = d3_selection of the nodeGroup + */ + _nodeGroupIndex: { + type: Object, + value: function() { return {}; } + }, + /* + * Dictionary for easily stylizing annotation nodes when state changes. + * _annotationGroupIndex[nodeName][hostNodeName] = + * d3_selection of the annotationGroup + */ + _annotationGroupIndex: { + type: Object, + value: function() { return {}; } + }, + /* + * Dictionary for easily stylizing edges when state changes. + * _edgeGroupIndex[edgeName] = d3_selection of the edgeGroup + */ + _edgeGroupIndex: { + type: Object, + value: function() { return {}; } + }, + /** + * Max font size for metanode label strings. + */ + maxMetanodeLabelLengthFontSize: { + type: Number, + value: 9 + }, + /** + * Min font size for metanode label strings. + */ + minMetanodeLabelLengthFontSize: { + type: Number, + value: 6 + }, + /** + * Metanode label strings longer than this are given smaller fonts. + */ + maxMetanodeLabelLengthLargeFont: { + type: Number, + value: 11 + }, + /** + * Metanode label strings longer than this are truncated with ellipses. + */ + maxMetanodeLabelLength: { + type: Number, + value: 18 + }, + progress: Object + }, + observers: [ + '_buildAndFit(graphHierarchy)' + ], + getNode: function(nodeName) { + return this.graphHierarchy.getRenderNodeByName(nodeName); + }, + isNodeExpanded: function(node) { + return node.expanded; + }, + setNodeExpanded: function(renderNode) { + this._build(this.graphHierarchy); + }, + /** + * Resets the state of the component. Called whenever the whole graph + * (dataset) changes. + */ + _resetState: function() { + // Reset the state of the component. + this._nodeGroupIndex = {}; + this._annotationGroupIndex = {}; + this._edgeGroupIndex = {}; + this._updateLabels(false); + // Remove all svg elements under the 'root' svg group. + d3.select(this.$.svg).select('#root').selectAll('*').remove(); + // And the defs. + d3.select(this.$.svg).select('defs #linearGradients') + .selectAll('*').remove(); + }, + /** Main method for building the scene */ + _build: function(graphHierarchy) { + if (!graphHierarchy) { return; } //handle untruthy input + var templateNames = d3.keys(graphHierarchy.hierarchy.templates); + + this.templateIndex = d3.scale.ordinal() + .domain(templateNames) + .range(d3.range(0, templateNames.length)); + tf.time('tf-graph-scene (layout):', function() { + // layout the scene for this meta / series node + tf.graph.layout.scene(graphHierarchy.root, this); + }.bind(this)); + + tf.time('tf-graph-scene (build scene):', function() { + tf.graph.scene.buildGroup(d3.select(this.$.root), graphHierarchy.root, this); + tf.graph.scene.addGraphClickListener(this.$.svg, this); + }.bind(this)); + // Update the minimap again when the graph is done animating. + setTimeout(function() { + this.minimap.update(); + }.bind(this), tf.graph.layout.PARAMS.animation.duration); + }, + ready: function() { + this._zoom = d3.behavior.zoom() + .on('zoomend', function() { + if (this._zoomStartCoords) { + // Calculate the total distance dragged during the zoom event. + // If it is sufficiently small, then fire an event indicating + // that zooming has ended. Otherwise wait to fire the zoom end + // event, so that a mouse click registered as part of this zooming + // is ignored (as this mouse click was part of a zooming, and should + // not be used to indicate an actual click on the graph). + var dragDistance = Math.sqrt( + Math.pow(this._zoomStartCoords[0] - this._zoomCoords[0], 2) + + Math.pow(this._zoomStartCoords[1] - this._zoomCoords[1], 2)); + if (dragDistance < this._maxZoomDistanceForClick) { + this._fireEnableClick(); + } else { + setTimeout(this._fireEnableClick.bind(this), 50); + } + } + this._zoomStartCoords = null; + }.bind(this)) + .on('zoom', function() { + // Store the coordinates of the zoom event + this._zoomCoords = d3.event.translate; + + // If this is the first zoom event after a zoom-end, then + // store the coordinates as the start coordinates as well, + // and fire an event to indicate that zooming has started. + // This doesn't use the zoomstart event, as d3 sends this + // event on mouse-down, even if there has been no dragging + // done to translate the graph around. + if (!this._zoomStartCoords) { + this._zoomStartCoords = this._zoomCoords.slice(); + this.fire('disable-click'); + } + this._zoomed = true; + d3.select(this.$.root).attr('transform', + 'translate(' + d3.event.translate + ')' + + 'scale(' + d3.event.scale + ')'); + // Notify the minimap. + this.minimap.zoom(d3.event.translate, d3.event.scale); + }.bind(this)); + d3.select(this.$.svg).call(this._zoom) + .on('dblclick.zoom', null); + d3.select(window).on('resize', function() { + // Notify the minimap that the user's window was resized. + // The minimap will figure out the new dimensions of the main svg + // and will use the existing translate and scale params. + this.minimap.zoom(); + }.bind(this)); + // Initialize the minimap. + this.minimap = this.$.minimap.init(this.$.svg, this.$.root, this._zoom, + tf.graph.layout.PARAMS.minimap.size, + tf.graph.layout.PARAMS.subscene.meta.labelHeight); + }, + _buildAndFit: function(graphHierarchy) { + this._resetState(); + this._build(graphHierarchy); + // Fit to screen after the graph is done animating. + setTimeout(this.fit.bind(this), tf.graph.layout.PARAMS.animation.duration); + }, + _updateLabels: function(showLabels) { + var titleStyle = this.getElementsByClassName('title')[0].style; + var auxTitleStyle = this.getElementsByClassName('auxTitle')[0].style; + var core = this.getElementsByClassName(tf.graph.scene.Class.Scene.CORE)[0]; + // Only show labels if the graph is fully loaded. + if (showLabels && core && this.progress && this.progress.value === 100) { + var aux = + this.getElementsByClassName(tf.graph.scene.Class.Scene.INEXTRACT)[0] || + this.getElementsByClassName(tf.graph.scene.Class.Scene.OUTEXTRACT)[0]; + var coreX = core.getCTM().e; + var auxX = aux ? aux.getCTM().e : null; + titleStyle.display = 'inline'; + titleStyle.left = coreX + 'px'; + if (auxX !== null && auxX !== coreX) { + auxTitleStyle.display = 'inline'; + auxTitleStyle.left = auxX + 'px'; + } else { + auxTitleStyle.display = 'none'; + } + } else { + titleStyle.display='none'; + auxTitleStyle.display = 'none'; + } + }, + + + + + /** + * Called whenever the user changed the 'color by' option in the + * UI controls. + */ + _colorByChanged: function() { + // We iterate through each svg node and update its state. + _.each(this._nodeGroupIndex, function(nodeGroup, nodeName) { + this._updateNodeState(nodeName); + }, this); + // Notify also the minimap. + this.minimap.update(); + }, + fit: function() { + tf.graph.scene.fit(this.$.svg, this.$.root, this._zoom, function() { + this._zoomed = false; + }.bind(this)); + }, + isNodeSelected: function(n) { + return n === this.selectedNode; + }, + isNodeHighlighted: function(n) { + return n === this.highlightedNode; + }, + addAnnotationGroup: function(a, d, selection) { + var an = a.node.name; + this._annotationGroupIndex[an] = this._annotationGroupIndex[an] || {}; + this._annotationGroupIndex[an][d.node.name] = selection; + }, + getAnnotationGroupsIndex: function(a) { + return this._annotationGroupIndex[a]; + }, + removeAnnotationGroup: function(a, d) { + delete this._annotationGroupIndex[a.node.name][d.node.name]; + }, + addNodeGroup: function(n, selection) { + this._nodeGroupIndex[n] = selection; + }, + getNodeGroup: function(n) { + return this._nodeGroupIndex[n]; + }, + removeNodeGroup: function(n) { + delete this._nodeGroupIndex[n]; + }, + addEdgeGroup: function(n, selection) { + this._edgeGroupIndex[e] = selection; + }, + getEdgeGroup: function(e) { + return this._edgeGroupIndex[e]; + }, + /** + * Update node and annotation node of the given name. + * @param {String} n node name + */ + _updateNodeState: function(n) { + var node = this.getNode(n); + var nodeGroup = this.getNodeGroup(n); + + if (nodeGroup) { + tf.graph.scene.node.stylize(nodeGroup, node, this); + } + + var annotationGroupIndex = this.getAnnotationGroupsIndex(n); + _.each(annotationGroupIndex, function(aGroup, hostName) { + tf.graph.scene.node.stylize(aGroup, node, this, + tf.graph.scene.Class.Annotation.NODE); + }, this); + }, + + _selectedNodeChanged: function(selectedNode, oldSelectedNode) { + if (selectedNode === oldSelectedNode) { + return; + } + + if (selectedNode) { + this._updateNodeState(selectedNode); + } + if (oldSelectedNode) { + this._updateNodeState(oldSelectedNode); + } + + if (!selectedNode) { + return; + } + // Update the minimap to reflect the highlighted (selected) node. + this.minimap.update(); + var node = this.graphHierarchy.hierarchy.node(selectedNode); + var nodeParents = []; + // Create list of all metanode parents of the selected node. + while (node.parentNode != null + && node.parentNode.name != tf.graph.ROOT_NAME) { + node = node.parentNode; + nodeParents.push(node.name); + } + // Ensure each parent metanode is built and expanded. + var topParentNodeToBeExpanded; + _.forEachRight(nodeParents, function(parentName) { + this.graphHierarchy.buildSubhierarchy(parentName); + var renderNode = this.graphHierarchy.getRenderNodeByName(parentName); + if (renderNode.node.isGroupNode && !renderNode.expanded) { + renderNode.expanded = true; + if (!topParentNodeToBeExpanded) { + topParentNodeToBeExpanded = renderNode; + } + } + }, this); + // If any expansion was needed to display this selected node, then + // inform the scene of the top-most expansion. + if (topParentNodeToBeExpanded) { + this.setNodeExpanded(topParentNodeToBeExpanded); + this._zoomed = true; + } + + if (tf.graph.scene.panToNode(selectedNode, this.$.svg, this.$.root, + this._zoom)) { + this._zoomed = true; + } + }, + _highlightedNodeChanged: function(highlightedNode, oldHighlightedNode) { + if (highlightedNode === oldHighlightedNode) { + return; + } + + if (highlightedNode) { + this._updateNodeState(highlightedNode); + } + if (oldHighlightedNode) { + this._updateNodeState(oldHighlightedNode); + } + }, + _onZoomChanged: function() { + this._updateLabels(!this._zoomed); + }, + _fireEnableClick: function() { + this.fire('enable-click'); + }, +}); +</script> |