diff options
Diffstat (limited to 'tensorflow/tensorboard/components/tf_graph_common/lib/scene/scene.ts')
-rw-r--r-- | tensorflow/tensorboard/components/tf_graph_common/lib/scene/scene.ts | 280 |
1 files changed, 188 insertions, 92 deletions
diff --git a/tensorflow/tensorboard/components/tf_graph_common/lib/scene/scene.ts b/tensorflow/tensorboard/components/tf_graph_common/lib/scene/scene.ts index f8ad90a968..518d89e82a 100644 --- a/tensorflow/tensorboard/components/tf_graph_common/lib/scene/scene.ts +++ b/tensorflow/tensorboard/components/tf_graph_common/lib/scene/scene.ts @@ -13,102 +13,121 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ module tf.graph.scene { + const svgNamespace = 'http://www.w3.org/2000/svg'; -/** Enums element class of objects in the scene */ -export let Class = { - Node: { - // <g> element that contains nodes. - CONTAINER: 'nodes', - // <g> element that contains detail about a node. - GROUP: 'node', - // <g> element that contains visual elements (like rect, ellipse). - SHAPE: 'nodeshape', - // <*> element(s) under SHAPE that should receive color updates. - COLOR_TARGET: 'nodecolortarget', - // <text> element showing the node's label. - LABEL: 'nodelabel', - // <g> element that contains all visuals for the expand/collapse - // button for expandable group nodes. - BUTTON_CONTAINER: 'buttoncontainer', - // <circle> element that surrounds expand/collapse buttons. - BUTTON_CIRCLE: 'buttoncircle', - // <path> element of the expand button. - EXPAND_BUTTON: 'expandbutton', - // <path> element of the collapse button. - COLLAPSE_BUTTON: 'collapsebutton' - }, - Edge: { - CONTAINER: 'edges', - GROUP: 'edge', - LINE: 'edgeline', - REF_LINE: 'refline', - STRUCTURAL: 'structural' - }, - Annotation: { - OUTBOX: 'out-annotations', - INBOX: 'in-annotations', - GROUP: 'annotation', - NODE: 'annotation-node', - EDGE: 'annotation-edge', - CONTROL_EDGE: 'annotation-control-edge', - LABEL: 'annotation-label', - ELLIPSIS: 'annotation-ellipsis' - }, - Scene: { - GROUP: 'scene', - CORE: 'core', - INEXTRACT: 'in-extract', - OUTEXTRACT: 'out-extract' - }, - Subscene: {GROUP: 'subscene'}, - OPNODE: 'op', - METANODE: 'meta', - SERIESNODE: 'series', - BRIDGENODE: 'bridge', - ELLIPSISNODE: 'ellipsis' -}; + /** Enums element class of objects in the scene */ + export let Class = { + Node: { + // <g> element that contains nodes. + CONTAINER: 'nodes', + // <g> element that contains detail about a node. + GROUP: 'node', + // <g> element that contains visual elements (like rect, ellipse). + SHAPE: 'nodeshape', + // <*> element(s) under SHAPE that should receive color updates. + COLOR_TARGET: 'nodecolortarget', + // <text> element showing the node's label. + LABEL: 'nodelabel', + // <g> element that contains all visuals for the expand/collapse + // button for expandable group nodes. + BUTTON_CONTAINER: 'buttoncontainer', + // <circle> element that surrounds expand/collapse buttons. + BUTTON_CIRCLE: 'buttoncircle', + // <path> element of the expand button. + EXPAND_BUTTON: 'expandbutton', + // <path> element of the collapse button. + COLLAPSE_BUTTON: 'collapsebutton' + }, + Edge: { + CONTAINER: 'edges', + GROUP: 'edge', + LINE: 'edgeline', + REF_LINE: 'refline', + STRUCTURAL: 'structural' + }, + Annotation: { + OUTBOX: 'out-annotations', + INBOX: 'in-annotations', + GROUP: 'annotation', + NODE: 'annotation-node', + EDGE: 'annotation-edge', + CONTROL_EDGE: 'annotation-control-edge', + LABEL: 'annotation-label', + ELLIPSIS: 'annotation-ellipsis' + }, + Scene: { + GROUP: 'scene', + CORE: 'core', + INEXTRACT: 'in-extract', + OUTEXTRACT: 'out-extract' + }, + Subscene: {GROUP: 'subscene'}, + OPNODE: 'op', + METANODE: 'meta', + SERIESNODE: 'series', + BRIDGENODE: 'bridge', + ELLIPSISNODE: 'ellipsis' + }; -/** - * Helper method for fitting the graph in the svg view. - * - * @param svg The main svg. - * @param zoomG The svg group used for panning and zooming. - * @param d3zoom The zoom behavior. - * @param callback Called when the fitting is done. - */ -export function fit(svg, zoomG, d3zoom, callback) { - let svgRect = svg.getBoundingClientRect(); - let sceneSize = null; - try { - sceneSize = zoomG.getBBox(); - if (sceneSize.width === 0) { - // There is no scene anymore. We have been detached from the dom. + /** + * A health pill encapsulates an overview of tensor element values. The value + * field is a list of 12 numbers that shed light on the status of the tensor. + * Visualized in health pills are the 3rd through 8th (inclusive) numbers of + * health pill values. Those 6 numbers are counts of tensor elements that fall + * under -Inf, negative, 0, positive, +Inf, NaN (in that order). + * + * Please keep this interface consistent with HealthPillDatum within + * backend.ts. + */ + export interface HealthPill { + node_name: string; + output_slot: number; + value: number[]; + wall_time: number; + step: number; + } + ; + + /** + * Helper method for fitting the graph in the svg view. + * + * @param svg The main svg. + * @param zoomG The svg group used for panning and zooming. + * @param d3zoom The zoom behavior. + * @param callback Called when the fitting is done. + */ + export function fit(svg, zoomG, d3zoom, callback) { + let svgRect = svg.getBoundingClientRect(); + let sceneSize = null; + try { + sceneSize = zoomG.getBBox(); + if (sceneSize.width === 0) { + // There is no scene anymore. We have been detached from the dom. + return; + } + } catch (e) { + // Firefox produced NS_ERROR_FAILURE if we have been + // detached from the dom. return; } - } catch (e) { - // Firefox produced NS_ERROR_FAILURE if we have been - // detached from the dom. - return; - } - let scale = 0.9 * Math.min( - svgRect.width / sceneSize.width, - svgRect.height / sceneSize.height, - 2 - ); - let params = layout.PARAMS.graph; - let zoomEvent = - d3zoom.scale(scale) - .on('zoomend.fitted', - () => { - // Remove the listener for the zoomend event, - // so we don't get called at the end of regular zoom events, - // just those that fit the graph to screen. - d3zoom.on('zoomend.fitted', null); - callback(); - }) - .translate([params.padding.paddingLeft, params.padding.paddingTop]) - .event; - d3.select(zoomG).transition().duration(500).call(zoomEvent); + let scale = 0.9 * + Math.min( + svgRect.width / sceneSize.width, svgRect.height / sceneSize.height, + 2); + let params = layout.PARAMS.graph; + let zoomEvent = + d3zoom.scale(scale) + .on('zoomend.fitted', + () => { + // Remove the listener for the zoomend event, + // so we don't get called at the end of regular zoom events, + // just those that fit the graph to screen. + d3zoom.on('zoomend.fitted', null); + callback(); + }) + .translate([params.padding.paddingLeft, params.padding.paddingTop]) + .event; + d3.select(zoomG).transition().duration(500).call(zoomEvent); }; /** @@ -450,4 +469,81 @@ export function positionEllipse(ellipse, cx: number, cy: number, }); }; +/** + * Renders a health pill for an op atop a node. + */ +function _addHealthPill( + nodeGroupElement: SVGElement, healthPills: HealthPill[], + nodeInfo: render.RenderGroupNodeInfo, colors: string[]) { + // Check if text already exists at location. + d3.select(nodeGroupElement.parentNode) + .selectAll('.health-pill-group') + .remove(); + + if (!nodeInfo || !healthPills || !healthPills.length) { + return; + } + + let lastHealthPillData = healthPills[healthPills.length - 1].value; + + // For now, we only visualize the 6 values that summarize counts of tensor + // elements of various categories: -Inf, negative, 0, positive, Inf, and NaN. + let lastHealthPillOverview = lastHealthPillData.slice(2, 8); + + let healthPillWidth = 60; + let healthPillHeight = 10; + let healthPill = document.createElementNS(svgNamespace, 'svg'); + healthPill.classList.add('health-pill-group'); + healthPill.setAttribute('width', String(healthPillWidth)); + healthPill.setAttribute('height', String(healthPillHeight)); + + let totalCount = lastHealthPillData[1]; + // Create 1 rectangle for each category. + let totalCountSoFar = 0; + let totalWidthDividedByTotalCount = healthPillWidth / totalCount; + for (let i = 0; i < lastHealthPillOverview.length; i++) { + if (!lastHealthPillOverview[i]) { + // Do not render empty rectangles. + continue; + } + let rect = document.createElementNS(svgNamespace, 'rect'); + rect.setAttribute('height', String(healthPillHeight)); + rect.setAttribute( + 'width', + String(totalWidthDividedByTotalCount * lastHealthPillOverview[i])); + rect.setAttribute( + 'x', String(totalWidthDividedByTotalCount * totalCountSoFar)); + rect.setAttribute('fill', colors[i]); + totalCountSoFar += lastHealthPillOverview[i]; + Polymer.dom(healthPill).appendChild(rect); + } + + // Center this health pill just right above the node for the op. + healthPill.setAttribute( + 'x', String(nodeInfo.x - healthPillWidth + nodeInfo.width / 2)); + healthPill.setAttribute( + 'y', + String(nodeInfo.y - healthPillHeight - nodeInfo.coreBox.height / 2 - 2)); + Polymer.dom(nodeGroupElement.parentNode).appendChild(healthPill); +} + +/** + * Adds health pills (which visualize tensor summaries) to a graph group. + * @param svgRoot The root SVG element of the graph to add heath pills to. + * @param nodeNamesToHealthPills An object mapping node names to health pills. + * @param colors A list of colors to use. + */ +export function addHealthPills( + svgRoot: SVGElement, nodeNamesToHealthPills: {[key: string]: HealthPill[]}, + colors: string[]) { + let svgRootSelection = d3.select(svgRoot); + + svgRootSelection.selectAll('g.nodeshape') + .each(function(nodeInfo: render.RenderGroupNodeInfo) { + // The element is the first item of a d3 selection. + _addHealthPill( + this, nodeNamesToHealthPills[nodeInfo.node.name], nodeInfo, colors); + }); +}; + } // close module |