aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/common.ts17
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts128
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts55
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts7
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/render.ts26
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts62
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts107
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts3
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts139
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/scene/scene.ts16
-rw-r--r--tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html27
-rw-r--r--tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html15
-rw-r--r--tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html6
-rw-r--r--tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html8
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph-style.html7
-rw-r--r--tensorflow/tensorboard/components/tf-graph/tf-graph.html14
16 files changed, 445 insertions, 192 deletions
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/common.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/common.ts
index 8af6f5d74e..202426784d 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/common.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/common.ts
@@ -100,7 +100,7 @@ export function time<T>(msg: string, task: () => T) {
export interface ProgressTracker {
updateProgress(incrementValue: number): void;
setMessage(msg: string): void;
- reportError(msg: string): void;
+ reportError(msg: string, err: Error): void;
}
/**
@@ -124,7 +124,10 @@ export function getTracker(polymerComponent: any) {
msg: polymerComponent.progress.msg
});
},
- reportError: function(msg) {
+ reportError: function(msg: string, err) {
+ // Log the stack trace in the console.
+ console.error(err.stack);
+ // And send a user-friendly message to the UI.
polymerComponent.set("progress", {
value: polymerComponent.progress.value,
msg: msg,
@@ -146,7 +149,7 @@ export function getSubtaskTracker(parentTracker: ProgressTracker,
setMessage: function(progressMsg) {
// The parent should show a concatenation of its message along with
// its subtask tracker message.
- parentTracker.setMessage(subtaskMsg + " : " + progressMsg);
+ parentTracker.setMessage(subtaskMsg + ": " + progressMsg);
},
updateProgress: function(incrementValue) {
// Update the parent progress relative to the child progress.
@@ -155,10 +158,10 @@ export function getSubtaskTracker(parentTracker: ProgressTracker,
parentTracker
.updateProgress(incrementValue * impactOnTotalProgress / 100);
},
- reportError: function(errorMsg) {
+ reportError: function(msg: string, err: Error) {
// The parent should show a concatenation of its message along with
// its subtask error message.
- parentTracker.reportError(subtaskMsg + " : " + errorMsg);
+ parentTracker.reportError(subtaskMsg + ": " + msg, err);
}
};
}
@@ -181,7 +184,9 @@ export function runAsyncTask<T>(msg: string, incProgressValue: number,
// Return the result to be used by other tasks.
resolve(result);
} catch (e) {
- reject(result);
+ // Errors that happen inside asynchronous tasks are
+ // reported to the tracker using a user-friendly message.
+ tracker.reportError("Failed " + msg, e);
}
}, ASYNC_TASK_DELAY);
});
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
index af58f46032..de9c6360b6 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
@@ -34,6 +34,9 @@ export enum InclusionType {INCLUDE, EXCLUDE, UNSPECIFIED};
/** Indicates if a series is to be grouped in the graph when rendered. */
export enum SeriesGroupingType {GROUP, UNGROUP};
+/** Attribute key reserved for the shapes of the output tensors. */
+const OUTPUT_SHAPES_KEY = "_output_shapes";
+
/**
* A BaseEdge is the label object (in the graphlib sense) for an edge in the
* original, full graph produced after parsing. Subsequent graphs, like those
@@ -111,16 +114,31 @@ export interface Node {
include: InclusionType;
}
+export type TensorShape = number[];
+
export interface OpNode extends Node {
op: string;
device: string;
- attr: {key: string, value: Object}[];
+ attr: {key: string, value: any}[];
inputs: NormalizedInput[];
inEmbeddings: OpNode[];
outEmbeddings: OpNode[];
// The name of the SeriesNode that can contain this node in its series.
// If there is no such node, then this is null.
owningSeries: string;
+ /**
+ * Array of tensor shapes. Null if the number of output tensors is unknown,
+ * otherwise the length will equal the number of output tensors.
+ *
+ * Each tensor shape is an array of numbers, or null. Details:
+ * - null means unknown rank, and therefore entire shape is unknown.
+ * - [4, 2, 1] means rank-3 tensor of size 4x2x1.
+ * - [] means a scalar (rank-0 tensor).
+ * - [1] means rank-1 tensor of size 1 (not the same as scalar).
+ * - [5, -1, 3] means rank-3 tensor of shape is 5x?x3. The size
+ * of the middle dimension is unknown (encoded as -1).
+ */
+ outputShapes: TensorShape[];
}
export interface BridgeNode extends Node {
@@ -304,7 +322,7 @@ class OpNodeImpl implements OpNode {
op: string;
device: string;
stats: NodeStats;
- attr: {key: string, value: Object}[];
+ attr: {key: string, value: any}[];
inputs: NormalizedInput[];
type: NodeType;
isGroupNode: boolean;
@@ -314,22 +332,24 @@ class OpNodeImpl implements OpNode {
parentNode: Node;
include: InclusionType;
owningSeries: string;
+ outputShapes: TensorShape[];
/**
* Constructs a new Op node.
*
* @param rawNode The raw node.
- * @param normalizedInputs An array of normalized
- * inputs that denote the incoming edges to the current node. Each input
- * contains the normalized name of the source node, whether it has a
- * number part and whether it is a control dependency.
*/
- constructor(rawNode: tf.TFNode, normalizedInputs: NormalizedInput[]) {
+ constructor(rawNode: tf.TFNode) {
this.op = rawNode.op;
this.name = rawNode.name;
this.device = rawNode.device;
this.attr = rawNode.attr;
- this.inputs = normalizedInputs;
+ // An array of normalized inputs that denote the incoming edges to
+ // the current node. Each input contains the normalized name of the
+ // source node, whether it has a number part and whether it is a
+ // control dependency.
+ this.inputs = normalizeInputs(rawNode.input);
+ this.outputShapes = extractOutputShapes(rawNode.attr);
// additional properties
this.type = NodeType.OP;
this.isGroupNode = false;
@@ -548,7 +568,12 @@ export interface Metaedge extends graphlib.EdgeObject {
*/
numRefEdges: number;
- addBaseEdge(edge: BaseEdge): void;
+ /**
+ * Total size (number of units) of all the tensors flowing through this edge.
+ */
+ totalSize: number;
+
+ addBaseEdge(edge: BaseEdge, h: hierarchy.Hierarchy): void;
}
export function createMetaedge(v: string, w: string): Metaedge {
@@ -566,6 +591,7 @@ class MetaedgeImpl implements Metaedge {
numRegularEdges: number;
numControlEdges: number;
numRefEdges: number;
+ totalSize: number;
constructor(v: string, w: string) {
this.v = v;
@@ -575,9 +601,10 @@ class MetaedgeImpl implements Metaedge {
this.numRegularEdges = 0;
this.numControlEdges = 0;
this.numRefEdges = 0;
+ this.totalSize = 0;
}
- addBaseEdge(edge: BaseEdge): void {
+ addBaseEdge(edge: BaseEdge, h: hierarchy.Hierarchy): void {
this.baseEdgeList.push(edge);
if (edge.isControlDependency) {
this.numControlEdges += 1;
@@ -587,6 +614,38 @@ class MetaedgeImpl implements Metaedge {
if (edge.isReferenceEdge) {
this.numRefEdges += 1;
}
+ // Compute the size of the tensor flowing through this
+ // base edge.
+ this.totalSize += MetaedgeImpl.computeSizeOfEdge(edge, h);
+ }
+
+ private static computeSizeOfEdge(edge: BaseEdge, h: hierarchy.Hierarchy)
+ : number {
+ let opNode = <OpNode> h.node(edge.v);
+ if (opNode.outputShapes == null) {
+ // No shape information. Asssume a single number. This gives
+ // a lower bound for the total size.
+ return 1;
+ }
+ // Sum the sizes of all output tensors.
+ return _(opNode.outputShapes).map(shape => {
+ // If the shape is unknown, treat it as 1 when computing
+ // total size. This gives a lower bound for the total size.
+ if (shape == null) {
+ return 1;
+ }
+ // Multiply all shapes to get the total size of the tensor.
+ // E.g. The total size of [4, 2, 1] is 4 * 2 * 1.
+ return _(shape).reduce((accumulated, currSize) => {
+ // If this particular dimension is unknown, treat
+ // it as 1 when computing total size. This gives a lower bound
+ // for the total size.
+ if (currSize === -1) {
+ currSize = 1;
+ }
+ return accumulated * currSize;
+ }, 1);
+ }).sum();
}
}
@@ -647,6 +706,49 @@ class SeriesNodeImpl implements SeriesNode {
}
/**
+ * Extracts the shapes of the output tensors from the attr property in the
+ * node proto.
+ */
+function extractOutputShapes(attr: {key: string, value: any}[]): TensorShape[] {
+ let result = null;
+ // We don't know anything about the output tensors.
+ if (!attr) {
+ return null;
+ }
+ for (let i = 0; i < attr.length; i++) {
+ let {key, value} = attr[i];
+ if (key === OUTPUT_SHAPES_KEY) {
+ // Map all output tensors into array of numbers denoting their shape.
+ let result = value.list.shape.map(shape => {
+ if (shape.unknown_rank) {
+ // This output tensor is of unknown rank. We don't know if it is a
+ // scalar, or a tensor, or of what shape it is.
+ return null;
+ }
+ if (shape.dim == null ||
+ (shape.dim.length === 1 && shape.dim[0].size == null)) {
+ // This output tensor is a scalar.
+ return [];
+ }
+ // This output tensor has a known rank. Map each dimension size
+ // into a number.
+ return shape.dim.map(dim => {
+ // Size can be -1 if this particular dimension is unknown.
+ return dim.size;
+ });
+ });
+ // Since we already processed it, remove the entry from the attribute
+ // list (saves memory).
+ attr.splice(i, 1);
+ return result;
+ }
+ }
+ // We didn't find OUTPUT_SHAPES_KEY in attributes, so we don't know anything
+ // about the output tensors.
+ return result;
+}
+
+/**
* Normalizes the inputs and extracts associated metadata:
* 1) Inputs can contain a colon followed by a number at the end
* (e.g. inputName:1) and we remove this from the input name, and take note
@@ -729,8 +831,7 @@ export function build(rawNodes: tf.TFNode[], params: BuildParams,
let opNodes = new Array<OpNode>(rawNodes.length);
let index = 0;
_.each(rawNodes, rawNode => {
- let normalizedInputs = normalizeInputs(rawNode.input);
- let opNode = new OpNodeImpl(rawNode, normalizedInputs);
+ let opNode = new OpNodeImpl(rawNode);
if (isInEmbeddedPred(opNode)) {
embeddingNodeNames.push(opNode.name);
inEmbedding[opNode.name] = opNode;
@@ -819,9 +920,6 @@ export function build(rawNodes: tf.TFNode[], params: BuildParams,
return graph;
}, tracker);
- })
- .catch(function(reason) {
- throw new Error("Failure creating graph");
});
};
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts
index 0af9905924..2895efc8bb 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/hierarchy.ts
@@ -150,7 +150,7 @@ class HierarchyImpl implements Hierarchy {
// Copy the BaseEdge from the parent's Metaedge into this
// bridgegraph Metaedge.
- bridgeMetaedge.addBaseEdge(baseEdge);
+ bridgeMetaedge.addBaseEdge(baseEdge, this);
});
})
.value(); // force lodash chain execution.
@@ -250,16 +250,14 @@ class HierarchyImpl implements Hierarchy {
getOneWayEdges(node: GroupNode|OpNode, inEdges: boolean) {
let edges = { control: [], regular: [] };
// A node with no parent cannot have any edges.
- if (!node.parentNode) {
- return edges;
- }
- if (node.parentNode.isGroupNode) {
- let parentNode = <GroupNode>node.parentNode;
- let metagraph = parentNode.metagraph;
- let bridgegraph = this.getBridgegraph(parentNode.name);
- findEdgeTargetsInGraph(metagraph, node, inEdges, edges);
- findEdgeTargetsInGraph(bridgegraph, node, inEdges, edges);
+ if (!node.parentNode || !node.parentNode.isGroupNode) {
+ return edges;
}
+ let parentNode = <GroupNode> node.parentNode;
+ let metagraph = parentNode.metagraph;
+ let bridgegraph = this.getBridgegraph(parentNode.name);
+ findEdgeTargetsInGraph(metagraph, node, inEdges, edges);
+ findEdgeTargetsInGraph(bridgegraph, node, inEdges, edges);
return edges;
}
@@ -365,20 +363,23 @@ class HierarchyImpl implements Hierarchy {
function findEdgeTargetsInGraph(
graph: graphlib.Graph<GroupNode|OpNode, Metaedge>,
node: Node, inbound: boolean, targets: Edges): void {
- _.each(<Metaedge[]> graph.edges(), e => {
- let [selfName, otherName] = inbound ? [e.w, e.v] : [e.v, e.w];
- if (selfName === node.name) {
- if (node.isGroupNode) {
- let targetList = graph.edge(e).numRegularEdges
- ? targets.regular : targets.control;
- targetList.push(otherName);
- } else {
- _.each(graph.edge(e).baseEdgeList, baseEdge => {
- let targetList = baseEdge.isControlDependency
- ? targets.control : targets.regular;
- targetList.push(inbound ? baseEdge.v : baseEdge.w);
- });
- }
+ let edges = inbound ? graph.inEdges(node.name) : graph.outEdges(node.name);
+ _.each(edges, e => {
+ let otherName = inbound ? e.v : e.w;
+ let metaedge = graph.edge(e);
+
+ if (node.isGroupNode && metaedge.baseEdgeList.length > 1) {
+ let targetList = metaedge.numRegularEdges
+ ? targets.regular : targets.control;
+ targetList.push(otherName);
+ } else {
+ // Enumerate all the base edges if the node is an OpNode, or the
+ // metaedge has only 1 edge in it.
+ _.each(metaedge.baseEdgeList, (baseEdge: BaseEdge) => {
+ let targetList = baseEdge.isControlDependency
+ ? targets.control : targets.regular;
+ targetList.push(inbound ? baseEdge.v : baseEdge.w);
+ });
}
});
}
@@ -428,8 +429,6 @@ export function build(graph: tf.graph.SlimGraph, params: HierarchyParams,
})
.then(() => {
return h;
- }).catch(function(reason) {
- throw new Error("Failure creating graph hierarchy");
});
};
@@ -552,10 +551,8 @@ function addEdges(h: Hierarchy, graph: SlimGraph,
!baseEdge.isControlDependency) {
sharedAncestorNode.hasNonControlEdges = true;
}
- metaedge.addBaseEdge(baseEdge);
-
+ metaedge.addBaseEdge(baseEdge, h);
});
-
};
/**
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts
index 0b8a308fe3..18ee1dcf8c 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/parser.ts
@@ -97,9 +97,6 @@ export function readAndParseData(dataset: {path: string, statsPath: string},
nodes: nodes,
statsJson: statsJson
};
- })
- .catch(function(reason) {
- throw new Error("Failure parsing graph definition");
});
}
@@ -139,7 +136,9 @@ export function parsePbtxt(input: string): TFNode[] {
"node.attr.value.list.type": true,
"node.attr.value.shape.dim": true,
"node.attr.value.tensor.string_val": true,
- "node.attr.value.tensor.tensor_shape.dim": true
+ "node.attr.value.tensor.tensor_shape.dim": true,
+ "node.attr.value.list.shape": true,
+ "node.attr.value.list.shape.dim": true
};
/**
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/render.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/render.ts
index df4119ef5b..f0ff1fbfc6 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/render.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/render.ts
@@ -79,6 +79,18 @@ export let SeriesNodeColors = {
DEFAULT_STROKE: "#b2b2b2"
};
+/** The minimum stroke width of an edge. */
+const MIN_EDGE_WIDTH = 0.75;
+
+/** The maximum stroke width of an edge. */
+const MAX_EDGE_WIDTH = 12;
+
+/** The exponent used in the power scale for edge thickness. */
+const EDGE_WIDTH_SCALE_EXPONENT = 0.3;
+
+/** The domain (min and max value) for the edge width. */
+const DOMAIN_EDGE_WIDTH_SCALE = [1, 5E6];
+
/**
* Parameters that affect how the graph is rendered on the screen.
*/
@@ -158,6 +170,7 @@ export class RenderGraphInfo {
private deviceColorMap: d3.scale.Ordinal<string, string>;
private memoryUsageScale: d3.scale.Linear<string, string>;
private computeTimeScale: d3.scale.Linear<string, string>;
+ edgeWidthScale: d3.scale.Pow<number, number>;
// Since the rendering information for each node is constructed lazily,
// upon node's expansion by the user, we keep a map between the node's name
// and whether the rendering information was already constructed for that
@@ -173,6 +186,12 @@ export class RenderGraphInfo {
.range(_.map(d3.range(hierarchy.devices.length),
MetanodeColors.DEVICE_PALETTE));
+ this.edgeWidthScale = d3.scale.pow()
+ .exponent(EDGE_WIDTH_SCALE_EXPONENT)
+ .domain(DOMAIN_EDGE_WIDTH_SCALE)
+ .range([MIN_EDGE_WIDTH, MAX_EDGE_WIDTH])
+ .clamp(true);
+
let topLevelGraph = hierarchy.root.metagraph;
// Find the maximum and minimum memory usage.
let memoryExtent = d3.extent(topLevelGraph.nodes(),
@@ -218,6 +237,13 @@ export class RenderGraphInfo {
}
/**
+ * Get the underlying node in the hierarchical graph by its name.
+ */
+ getNodeByName(nodeName: string): Node {
+ return this.hierarchy.node(nodeName);
+ }
+
+ /**
* Get a previously created RenderNodeInfo for the specified node name,
* or create one if it hasn't been created yet.
*/
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts
index 425eea0408..4301acc8d9 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/annotation.ts
@@ -39,11 +39,11 @@ module tf.graph.scene.annotation {
* @param container selection of the container.
* @param annotationData node.{in|out}Annotations
* @param d node to build group for.
- * @param sceneBehavior polymer scene element.
+ * @param sceneElement <tf-graph-scene> polymer element.
* @return selection of appended objects
*/
export function buildGroup(container, annotationData: render.AnnotationList,
- d: render.RenderNodeInfo, sceneBehavior) {
+ d: render.RenderNodeInfo, sceneElement) {
// Select all children and join with data.
let annotationGroups = container.selectAll(function() {
// using d3's selector function
@@ -60,7 +60,7 @@ export function buildGroup(container, annotationData: render.AnnotationList,
let aGroup = d3.select(this);
// Add annotation to the index in the scene
- sceneBehavior.addAnnotationGroup(a, d, aGroup);
+ sceneElement.addAnnotationGroup(a, d, aGroup);
// Append annotation edge
let edgeType = Class.Annotation.EDGE;
let metaedge = a.renderMetaedgeInfo && a.renderMetaedgeInfo.metaedge;
@@ -71,11 +71,11 @@ export function buildGroup(container, annotationData: render.AnnotationList,
if (metaedge && metaedge.numRefEdges) {
edgeType += " " + Class.Edge.REF_LINE;
}
- edge.appendEdge(aGroup, a, sceneBehavior, edgeType);
+ edge.appendEdge(aGroup, a, sceneElement, edgeType);
- if (a.annotationType !== tf.graph.render.AnnotationType.ELLIPSIS) {
+ if (a.annotationType !== render.AnnotationType.ELLIPSIS) {
addAnnotationLabelFromNode(aGroup, a);
- buildShape(aGroup, a, sceneBehavior);
+ buildShape(aGroup, a);
} else {
addAnnotationLabel(aGroup, a.node.name, a, Class.Annotation.ELLIPSIS);
}
@@ -89,9 +89,9 @@ export function buildGroup(container, annotationData: render.AnnotationList,
})
.each(function(a) {
let aGroup = d3.select(this);
- update(aGroup, d, a, sceneBehavior);
- if (a.annotationType !== tf.graph.render.AnnotationType.ELLIPSIS) {
- addInteraction(aGroup, d, a, sceneBehavior);
+ update(aGroup, d, a, sceneElement);
+ if (a.annotationType !== render.AnnotationType.ELLIPSIS) {
+ addInteraction(aGroup, d, a, sceneElement);
}
});
@@ -100,7 +100,7 @@ export function buildGroup(container, annotationData: render.AnnotationList,
let aGroup = d3.select(this);
// Remove annotation from the index in the scene
- sceneBehavior.removeAnnotationGroup(a, d, aGroup);
+ sceneElement.removeAnnotationGroup(a, d, aGroup);
})
.remove();
return annotationGroups;
@@ -110,13 +110,13 @@ export function buildGroup(container, annotationData: render.AnnotationList,
* Maps an annotation enum to a class name used in css rules.
*/
function annotationToClassName(annotationType: render.AnnotationType) {
- return (tf.graph.render.AnnotationType[annotationType] || "")
+ return (render.AnnotationType[annotationType] || "")
.toLowerCase() || null;
}
-function buildShape(aGroup, a: render.Annotation, sceneBehavior) {
- if (a.annotationType === tf.graph.render.AnnotationType.SUMMARY) {
- let summary = scene.selectOrCreateChild(aGroup, "use");
+function buildShape(aGroup, a: render.Annotation) {
+ if (a.annotationType === render.AnnotationType.SUMMARY) {
+ let summary = selectOrCreateChild(aGroup, "use");
summary.attr({
"class": "summary",
"xlink:href": "#summary-icon",
@@ -125,7 +125,7 @@ function buildShape(aGroup, a: render.Annotation, sceneBehavior) {
} else {
let shape = node.buildShape(aGroup, a, Class.Annotation.NODE);
// add title tag to get native tooltips
- scene.selectOrCreateChild(shape, "title").text(a.node.name);
+ selectOrCreateChild(shape, "title").text(a.node.name);
}
}
@@ -152,16 +152,16 @@ function addAnnotationLabel(aGroup, label, a, additionalClassNames,
}
function addInteraction(selection, d: render.RenderNodeInfo,
- annotation: tf.graph.render.Annotation, sceneBehavior) {
+ annotation: render.Annotation, sceneElement) {
selection
.on("mouseover", a => {
- sceneBehavior.fire("annotation-highlight", {
+ sceneElement.fire("annotation-highlight", {
name: a.node.name,
hostName: d.node.name
});
})
.on("mouseout", a => {
- sceneBehavior.fire("annotation-unhighlight", {
+ sceneElement.fire("annotation-unhighlight", {
name: a.node.name,
hostName: d.node.name
});
@@ -170,15 +170,15 @@ function addInteraction(selection, d: render.RenderNodeInfo,
// Stop this event"s propagation so that it isn't also considered a
// graph-select.
(<Event>d3.event).stopPropagation();
- sceneBehavior.fire("annotation-select", {
+ sceneElement.fire("annotation-select", {
name: a.node.name,
hostName: d.node.name
});
});
- if (annotation.annotationType !== tf.graph.render.AnnotationType.SUMMARY &&
- annotation.annotationType !== tf.graph.render.AnnotationType.CONSTANT) {
- selection.on("contextmenu", tf.graph.scene.contextmenu.getMenu(
- tf.graph.scene.node.getContextMenu(annotation.node, sceneBehavior)));
+ if (annotation.annotationType !== render.AnnotationType.SUMMARY &&
+ annotation.annotationType !== render.AnnotationType.CONSTANT) {
+ selection.on("contextmenu", contextmenu.getMenu(
+ node.getContextMenu(annotation.node, sceneElement)));
}
};
@@ -188,21 +188,21 @@ function addInteraction(selection, d: render.RenderNodeInfo,
* @param aGroup selection of a "g.annotation" element.
* @param d Host node data.
* @param a annotation node data.
- * @param scene Polymer scene element.
+ * @param scene <tf-graph-scene> polymer element.
*/
function update(aGroup, d: render.RenderNodeInfo, a: render.Annotation,
- sceneBehavior) {
+ sceneElement) {
let cx = layout.computeCXPositionOfNodeShape(d);
// Annotations that point to embedded nodes (constants,summary)
// don't have a render information attached so we don't stylize these.
// Also we don't stylize ellipsis annotations (the string "... and X more").
if (a.renderNodeInfo &&
- a.annotationType !== tf.graph.render.AnnotationType.ELLIPSIS) {
- node.stylize(aGroup, a.renderNodeInfo, sceneBehavior,
+ a.annotationType !== render.AnnotationType.ELLIPSIS) {
+ node.stylize(aGroup, a.renderNodeInfo, sceneElement,
Class.Annotation.NODE);
}
- if (a.annotationType === tf.graph.render.AnnotationType.SUMMARY) {
+ if (a.annotationType === render.AnnotationType.SUMMARY) {
// Update the width of the annotation to give space for the image.
a.width += 10;
}
@@ -224,11 +224,11 @@ function update(aGroup, d: render.RenderNodeInfo, a: render.Annotation,
});
// Node position (only one of the shape selection will be non-empty.)
- scene.positionEllipse(aGroup.select("." + Class.Annotation.NODE + " ellipse"),
+ positionEllipse(aGroup.select("." + Class.Annotation.NODE + " ellipse"),
cx + a.dx, d.y + a.dy, a.width, a.height);
- scene.positionRect(aGroup.select("." + Class.Annotation.NODE + " rect"),
+ positionRect(aGroup.select("." + Class.Annotation.NODE + " rect"),
cx + a.dx, d.y + a.dy, a.width, a.height);
- scene.positionRect(aGroup.select("." + Class.Annotation.NODE + " use"),
+ positionRect(aGroup.select("." + Class.Annotation.NODE + " use"),
cx + a.dx, d.y + a.dy, a.width, a.height);
// Edge position
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts
index 5ae6244e00..d0f1e8fad6 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts
@@ -19,10 +19,13 @@ limitations under the License.
module tf.graph.scene.edge {
+/** Delimiter between dimensions when showing sizes of tensors. */
+const TENSOR_SHAPE_DELIM = "×";
+
export type EdgeData = {v: string, w: string, label: render.RenderMetaedgeInfo};
export function getEdgeKey(edgeObj: EdgeData) {
- return edgeObj.v + tf.graph.EDGE_KEY_DELIM + edgeObj.w;
+ return edgeObj.v + EDGE_KEY_DELIM + edgeObj.w;
}
/**
@@ -41,14 +44,14 @@ export function getEdgeKey(edgeObj: EdgeData) {
*
* @param sceneGroup container
* @param graph
- * @param sceneBehavior Parent scene module.
+ * @param sceneElement <tf-graph-scene> polymer element.
* @return selection of the created nodeGroups
*/
export function buildGroup(sceneGroup,
- graph: graphlib.Graph<tf.graph.render.RenderNodeInfo,
- tf.graph.render.RenderMetaedgeInfo>, sceneBehavior) {
+ graph: graphlib.Graph<render.RenderNodeInfo, render.RenderMetaedgeInfo>,
+ sceneElement) {
let edges: EdgeData[] = [];
- let edgeData = _.reduce(graph.edges(), (edges, edgeObj) => {
+ edges = _.reduce(graph.edges(), (edges, edgeObj) => {
let edgeLabel = graph.edge(edgeObj);
edges.push({
v: edgeObj.v,
@@ -68,8 +71,7 @@ export function buildGroup(sceneGroup,
// See https://github.com/mbostock/d3/releases/tag/v2.0.0
// (It's not listed in the d3 wiki.)
return this.childNodes;
- })
- .data(edgeData, getEdgeKey);
+ }).data(edges, getEdgeKey);
// Make edges a group to support rendering multiple lines for metaedge
edgeGroups.enter()
@@ -80,7 +82,7 @@ export function buildGroup(sceneGroup,
let edgeGroup = d3.select(this);
d.label.edgeGroup = edgeGroup;
// index node group for quick highlighting
- sceneBehavior._edgeGroupIndex[getEdgeKey(d)] = edgeGroup;
+ sceneElement._edgeGroupIndex[getEdgeKey(d)] = edgeGroup;
// If any edges are reference edges, add the reference edge class.
let extraEdgeClass = d.label.metaedge && d.label.metaedge.numRefEdges
@@ -88,22 +90,57 @@ export function buildGroup(sceneGroup,
: undefined;
// Add line during enter because we're assuming that type of line
// normally does not change.
- appendEdge(edgeGroup, d, scene, extraEdgeClass);
+ appendEdge(edgeGroup, d, sceneElement, extraEdgeClass);
});
edgeGroups.each(position);
edgeGroups.each(function(d) {
- stylize(d3.select(this), d, sceneBehavior);
+ stylize(d3.select(this), d, sceneElement);
});
edgeGroups.exit()
.each(d => {
- delete sceneBehavior._edgeGroupIndex[getEdgeKey(d)];
+ delete sceneElement._edgeGroupIndex[getEdgeKey(d)];
})
.remove();
return edgeGroups;
};
+export function getShapeLabelFromNode(node: OpNode,
+ renderInfo: render.RenderGraphInfo) {
+ if (node.outputShapes == null || node.outputShapes.length === 0) {
+ return null;
+ }
+ // TODO(smilkov): Figure out exactly which output tensor this
+ // edge is from.
+ let shape = node.outputShapes[0];
+ if (shape == null) {
+ return null;
+ }
+ if (shape.length === 0) {
+ return "scalar";
+ }
+ return shape.map(size => {
+ return size === -1 ? "?" : size;
+ }).join(TENSOR_SHAPE_DELIM);
+}
+
+/**
+ * Creates the label for the given metaedge. If the metaedge consists
+ * of only 1 tensor, and it's shape is known, the label will contain that
+ * shape. Otherwise, the label will say the number of tensors in the metaedge.
+ */
+export function getLabelForEdge(metaedge: Metaedge,
+ renderInfo: render.RenderGraphInfo): string {
+ let isMultiEdge = metaedge.baseEdgeList.length > 1;
+ if (isMultiEdge) {
+ return metaedge.baseEdgeList.length + " tensors";
+ } else {
+ let node = <OpNode> renderInfo.getNodeByName(metaedge.baseEdgeList[0].v);
+ return getShapeLabelFromNode(node, renderInfo);
+ }
+}
+
/**
* For a given d3 selection and data object, create a path to represent the
* edge described in d.label.
@@ -112,15 +149,49 @@ export function buildGroup(sceneGroup,
* will sometimes be undefined, for example for some Annotation edges for which
* there is no underlying Metaedge in the hierarchical graph.
*/
-export function appendEdge(edgeGroup, d: EdgeData, sceneBehavior, edgeClass?) {
+export function appendEdge(edgeGroup, d: EdgeData,
+ sceneElement: {renderHierarchy: render.RenderGraphInfo},
+ edgeClass: string) {
+ let size = 1;
+ if (d.label != null && d.label.metaedge != null) {
+ // There is an underlying Metaedge.
+ size = d.label.metaedge.totalSize;
+ }
edgeClass = edgeClass || Class.Edge.LINE; // set default type
if (d.label && d.label.structural) {
edgeClass += " " + Class.Edge.STRUCTURAL;
}
+ // Give the path a unique id, which will be used to link
+ // the textPath (edge label) to this path.
+ let pathId = "path_" + getEdgeKey(d);
+ let strokeWidth = sceneElement.renderHierarchy.edgeWidthScale(size);
edgeGroup.append("path")
- .attr("class", edgeClass);
+ .attr({
+ "id": pathId,
+ "class": edgeClass,
+ }).style({
+ "stroke-width": strokeWidth + "px"
+ });
+
+ if (d.label == null || d.label.metaedge == null) {
+ // There is no associated metaedge, thus no text.
+ // This happens for annotation edges.
+ return;
+ }
+ let labelForEdge = getLabelForEdge(d.label.metaedge,
+ sceneElement.renderHierarchy);
+ if (labelForEdge == null) {
+ // We have no information to show on this edge.
+ return;
+ }
+ edgeGroup.append("text").append("textPath").attr({
+ "xlink:href": "#" + pathId,
+ "startOffset": "50%",
+ "text-anchor": "middle",
+ "dominant-baseline": "central"
+ }).text(labelForEdge);
};
export let interpolate = d3.svg.line<{x: number, y: number}>()
@@ -134,8 +205,9 @@ export let interpolate = d3.svg.line<{x: number, y: number}>()
function getEdgePathInterpolator(d: EdgeData, i: number, a: string) {
let renderMetaedgeInfo = <render.RenderMetaedgeInfo> d.label;
let adjoiningMetaedge = renderMetaedgeInfo.adjoiningMetaedge;
+ let points = renderMetaedgeInfo.points;
if (!adjoiningMetaedge) {
- return d3.interpolate(a, interpolate(renderMetaedgeInfo.points));
+ return d3.interpolate(a, interpolate(points));
}
let renderPath = this;
@@ -158,7 +230,6 @@ function getEdgePathInterpolator(d: EdgeData, i: number, a: string) {
// Update the relevant point in the renderMetaedgeInfo's points list, then
// re-interpolate the path.
- let points = renderMetaedgeInfo.points;
let index = inbound ? 0 : points.length - 1;
points[index].x = adjoiningPoint.x;
points[index].y = adjoiningPoint.y;
@@ -169,10 +240,8 @@ function getEdgePathInterpolator(d: EdgeData, i: number, a: string) {
function position(d) {
d3.select(this).select("path." + Class.Edge.LINE)
- .each(function(d) {
- let path = d3.select(this);
- path.transition().attrTween("d", getEdgePathInterpolator);
- });
+ .transition()
+ .attrTween("d", getEdgePathInterpolator);
};
/**
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts
index 32566c99ef..0be15da0e0 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts
@@ -262,7 +262,8 @@ export class Minimap {
downloadContext.drawImage(image, 0, 0,
this.downloadCanvas.width, this.downloadCanvas.height);
};
- image.src = "data:image/svg+xml;base64," + btoa(svgXml);
+ let blob = new Blob([svgXml], {type: "image/svg+xml;charset=utf-8"});
+ image.src = URL.createObjectURL(blob);
}
/**
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts
index 58ec821877..6d467c348a 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/node.ts
@@ -61,11 +61,11 @@ module tf.graph.scene.node {
*
* @param sceneGroup selection of the container
* @param nodeData array of render node information to map
- * @param sceneBehavior parent scene module
+ * @param sceneElement <tf-graph-scene> polymer element
* @return selection of the created nodeGroups
*/
export function buildGroup(sceneGroup,
- nodeData: render.RenderNodeInfo[], sceneBehavior) {
+ nodeData: render.RenderNodeInfo[], sceneElement) {
let container = scene.selectOrCreateChild(sceneGroup, "g",
Class.Node.CONTAINER);
// Select all children and join with data.
@@ -88,7 +88,7 @@ export function buildGroup(sceneGroup,
.each(function(d) {
let nodeGroup = d3.select(this);
// index node group for quick stylizing
- sceneBehavior.addNodeGroup(d.node.name, nodeGroup);
+ sceneElement.addNodeGroup(d.node.name, nodeGroup);
});
// UPDATE
@@ -98,58 +98,58 @@ export function buildGroup(sceneGroup,
})
.each(function(d) {
let nodeGroup = d3.select(this);
- // add g.in-annotations (always add -- to keep layer order consistent.)
+ // Add g.in-annotations (always add -- to keep layer order consistent.)
let inAnnotationBox = scene.selectOrCreateChild(nodeGroup, "g",
Class.Annotation.INBOX);
annotation.buildGroup(inAnnotationBox, d.inAnnotations, d,
- sceneBehavior);
+ sceneElement);
- // add g.out-annotations (always add -- to keep layer order consistent.)
+ // Add g.out-annotations (always add -- to keep layer order consistent.)
let outAnnotationBox = scene.selectOrCreateChild(nodeGroup, "g",
Class.Annotation.OUTBOX);
annotation.buildGroup(outAnnotationBox, d.outAnnotations, d,
- sceneBehavior);
+ sceneElement);
- // label
- let label = labelBuild(nodeGroup, d, sceneBehavior);
- // Do not add interaction to metanode labels as they live inside the
- // metanode shape which already has the same interactions.
- addInteraction(label, d, sceneBehavior, d.node.type === NodeType.META);
-
- // build .shape below label
- let shape = buildShape(nodeGroup, d, Class.Node.SHAPE, label.node());
+ // Build .shape first (background of the node).
+ let shape = buildShape(nodeGroup, d, Class.Node.SHAPE);
if (d.node.isGroupNode) {
- addButton(shape, d, sceneBehavior);
+ addButton(shape, d, sceneElement);
}
- addInteraction(shape, d, sceneBehavior);
+ addInteraction(shape, d, sceneElement);
- // build subscene on the top
+ // Build subscene on the top.
subsceneBuild(nodeGroup, <render.RenderGroupNodeInfo> d,
- sceneBehavior);
+ sceneElement);
+
+ // Build label last. Should be on top of everything else.
+ let label = labelBuild(nodeGroup, d, sceneElement);
+ // Do not add interaction to metanode labels as they live inside the
+ // metanode shape which already has the same interactions.
+ addInteraction(label, d, sceneElement, d.node.type === NodeType.META);
- stylize(nodeGroup, d, sceneBehavior);
- position(nodeGroup, d, sceneBehavior);
+ stylize(nodeGroup, d, sceneElement);
+ position(nodeGroup, d);
});
// EXIT
nodeGroups.exit()
.each(function(d) {
// remove all indices on remove
- sceneBehavior.removeNodeGroup(d.node.name);
+ sceneElement.removeNodeGroup(d.node.name);
let nodeGroup = d3.select(this);
if (d.inAnnotations.list.length > 0) {
nodeGroup.select("." + Class.Annotation.INBOX)
.selectAll("." + Class.Annotation.GROUP)
.each(a => {
- sceneBehavior.removeAnnotationGroup(a, d);
+ sceneElement.removeAnnotationGroup(a, d);
});
}
if (d.outAnnotations.list.length > 0) {
nodeGroup.select("." + Class.Annotation.OUTBOX)
.selectAll("." + Class.Annotation.GROUP)
.each(a => {
- sceneBehavior.removeAnnotationGroup(a, d);
+ sceneElement.removeAnnotationGroup(a, d);
});
}
})
@@ -163,17 +163,17 @@ export function buildGroup(sceneGroup,
*
* @param nodeGroup selection of the container
* @param renderNodeInfo the render information for the node.
- * @param sceneBehavior parent scene module
+ * @param sceneElement <tf-graph-scene> polymer element.
* @return Selection of the subscene group, or null if node group does not have
* a subscene. Op nodes, bridge nodes and unexpanded group nodes will
* not have a subscene.
*/
function subsceneBuild(nodeGroup,
- renderNodeInfo: render.RenderGroupNodeInfo, sceneBehavior) {
+ renderNodeInfo: render.RenderGroupNodeInfo, sceneElement) {
if (renderNodeInfo.node.isGroupNode) {
if (renderNodeInfo.expanded) {
// Recursively build the subscene.
- return scene.buildGroup(nodeGroup, renderNodeInfo, sceneBehavior,
+ return scene.buildGroup(nodeGroup, renderNodeInfo, sceneElement,
Class.Subscene.GROUP);
}
// Clean out existing subscene if the node is not expanded.
@@ -198,9 +198,9 @@ function subscenePosition(nodeGroup, d: render.RenderNodeInfo) {
*
* @param selection The group node selection.
* @param d Info about the node being rendered.
- * @param sceneBehavior parent scene module.
+ * @param sceneElement <tf-graph-scene> polymer element.
*/
-function addButton(selection, d: render.RenderNodeInfo, sceneBehavior) {
+function addButton(selection, d: render.RenderNodeInfo, sceneElement) {
let group = scene.selectOrCreateChild(
selection, "g", Class.Node.BUTTON_CONTAINER);
scene.selectOrCreateChild(group, "circle", Class.Node.BUTTON_CIRCLE);
@@ -212,7 +212,7 @@ function addButton(selection, d: render.RenderNodeInfo, sceneBehavior) {
// Stop this event's propagation so that it isn't also considered a
// node-select.
(<Event>d3.event).stopPropagation();
- sceneBehavior.fire("node-toggle-expand", { name: d.node.name });
+ sceneElement.fire("node-toggle-expand", { name: d.node.name });
});
scene.positionButton(group, d);
};
@@ -226,39 +226,39 @@ function addButton(selection, d: render.RenderNodeInfo, sceneBehavior) {
* given interaction would cause conflicts with the expand/collapse button.
*/
function addInteraction(selection, d: render.RenderNodeInfo,
- sceneBehavior, disableInteraction?: boolean) {
+ sceneElement, disableInteraction?: boolean) {
if (disableInteraction) {
selection.attr("pointer-events", "none");
return;
}
- let contextMenuFunction = tf.graph.scene.contextmenu.getMenu(
- getContextMenu(d.node, sceneBehavior));
+ let contextMenuFunction = contextmenu.getMenu(
+ getContextMenu(d.node, sceneElement));
selection.on("dblclick", d => {
- sceneBehavior.fire("node-toggle-expand", { name: d.node.name });
+ sceneElement.fire("node-toggle-expand", { name: d.node.name });
})
.on("mouseover", d => {
// don't send mouseover over expanded group,
// otherwise it is causing too much glitches
- if (sceneBehavior.isNodeExpanded(d)) { return; }
+ if (sceneElement.isNodeExpanded(d)) { return; }
- sceneBehavior.fire("node-highlight", { name: d.node.name });
+ sceneElement.fire("node-highlight", { name: d.node.name });
})
.on("mouseout", d => {
// don't send mouseover over expanded group,
// otherwise it is causing too much glitches
- if (sceneBehavior.isNodeExpanded(d)) { return; }
+ if (sceneElement.isNodeExpanded(d)) { return; }
- sceneBehavior.fire("node-unhighlight", { name: d.node.name });
+ sceneElement.fire("node-unhighlight", { name: d.node.name });
})
.on("click", d => {
// Stop this event's propagation so that it isn't also considered
// a graph-select.
(<Event>d3.event).stopPropagation();
- sceneBehavior.fire("node-select", { name: d.node.name });
+ sceneElement.fire("node-select", { name: d.node.name });
})
.on("contextmenu", (d, i) => {
- sceneBehavior.fire("node-select", { name: d.node.name });
+ sceneElement.fire("node-select", { name: d.node.name });
contextMenuFunction.call(d, i);
});
};
@@ -266,13 +266,13 @@ function addInteraction(selection, d: render.RenderNodeInfo,
/**
* Returns the d3 context menu specification for the provided node.
*/
-export function getContextMenu(node: Node, sceneBehavior) {
+export function getContextMenu(node: Node, sceneElement) {
let menu = [{
title: d => {
- return tf.graph.getIncludeNodeButtonString(node.include);
+ return getIncludeNodeButtonString(node.include);
},
action: (elm, d, i) => {
- sceneBehavior.fire("node-toggle-extract", { name: node.name });
+ sceneElement.fire("node-toggle-extract", { name: node.name });
}
}];
if (canBeInSeries(node)) {
@@ -281,7 +281,7 @@ export function getContextMenu(node: Node, sceneBehavior) {
return getGroupSettingLabel(node);
},
action: (elm, d, i) => {
- sceneBehavior.fire("node-toggle-seriesgroup",
+ sceneElement.fire("node-toggle-seriesgroup",
{ name: getSeriesName(node) });
}
});
@@ -343,10 +343,10 @@ export function getGroupSettingLabel(node: Node) {
* Append svg text for label and assign data.
* @param nodeGroup
* @param renderNodeInfo The render node information for the label.
- * @param sceneBehavior parent scene module.
+ * @param sceneElement <tf-graph-scene> polymer element.
*/
function labelBuild(nodeGroup, renderNodeInfo: render.RenderNodeInfo,
- sceneBehavior) {
+ sceneElement) {
let namePath = renderNodeInfo.node.name.split("/");
let text = namePath[namePath.length - 1];
@@ -355,13 +355,18 @@ function labelBuild(nodeGroup, renderNodeInfo: render.RenderNodeInfo,
!renderNodeInfo.expanded;
let label = scene.selectOrCreateChild(nodeGroup, "text", Class.Node.LABEL);
+
+ // Make sure the label is visually on top among its siblings.
+ let labelNode = <HTMLElement> label.node();
+ labelNode.parentNode.appendChild(labelNode);
+
label.attr("dy", ".35em")
.attr("text-anchor", "middle");
if (useFontScale) {
- if (text.length > sceneBehavior.maxMetanodeLabelLength) {
- text = text.substr(0, sceneBehavior.maxMetanodeLabelLength - 2) + "...";
+ if (text.length > sceneElement.maxMetanodeLabelLength) {
+ text = text.substr(0, sceneElement.maxMetanodeLabelLength - 2) + "...";
}
- let scale = getLabelFontScale(sceneBehavior);
+ let scale = getLabelFontScale(sceneElement);
label.attr("font-size", scale(text.length) + "px");
}
label.text(text);
@@ -373,13 +378,13 @@ function labelBuild(nodeGroup, renderNodeInfo: render.RenderNodeInfo,
* initialized once by getLabelFontScale.
*/
let fontScale = null;
-function getLabelFontScale(sceneBehavior) {
+function getLabelFontScale(sceneElement) {
if (!fontScale) {
fontScale = d3.scale.linear()
- .domain([sceneBehavior.maxMetanodeLabelLengthLargeFont,
- sceneBehavior.maxMetanodeLabelLength])
- .range([sceneBehavior.maxMetanodeLabelLengthFontSize,
- sceneBehavior.minMetanodeLabelLengthFontSize]).clamp(true);
+ .domain([sceneElement.maxMetanodeLabelLengthLargeFont,
+ sceneElement.maxMetanodeLabelLength])
+ .range([sceneElement.maxMetanodeLabelLengthFontSize,
+ sceneElement.minMetanodeLabelLengthFontSize]).clamp(true);
}
return fontScale;
}
@@ -401,13 +406,11 @@ function labelPosition(nodeGroup, cx: number, cy: number,
* @param nodeGroup
* @param d Render node information.
* @param nodeClass class for the element.
- * @param before Reference DOM node for insertion.
* @return Selection of the shape.
*/
-export function buildShape(nodeGroup, d, nodeClass: string, before?) {
+export function buildShape(nodeGroup, d, nodeClass: string) {
// Create a group to house the underlying visual elements.
- let shapeGroup = scene.selectOrCreateChild(nodeGroup, "g", nodeClass,
- before);
+ let shapeGroup = scene.selectOrCreateChild(nodeGroup, "g", nodeClass);
// TODO(jimbo): DOM structure should be templated in HTML somewhere, not JS.
switch (d.node.type) {
case NodeType.OP:
@@ -458,7 +461,7 @@ export function nodeClass(d: render.RenderNodeInfo) {
};
/** Modify node and its subscene and its label's positional attributes */
-function position(nodeGroup, d: render.RenderNodeInfo, sceneBehavior) {
+function position(nodeGroup, d: render.RenderNodeInfo) {
let shapeGroup = scene.selectChild(nodeGroup, "g", Class.Node.SHAPE);
let cx = layout.computeCXPositionOfNodeShape(d);
switch (d.node.type) {
@@ -520,15 +523,15 @@ export enum ColorBy { STRUCTURE, DEVICE, COMPUTE_TIME, MEMORY };
*/
export function getFillForNode(templateIndex, colorBy,
renderInfo: render.RenderNodeInfo, isExpanded: boolean): string {
- let colorParams = tf.graph.render.MetanodeColors;
+ let colorParams = render.MetanodeColors;
switch (colorBy) {
case ColorBy.STRUCTURE:
- if (renderInfo.node.type === tf.graph.NodeType.META) {
+ if (renderInfo.node.type === NodeType.META) {
let tid = (<Metanode>renderInfo.node).templateId;
return tid === null ?
colorParams.UNKNOWN :
colorParams.STRUCTURE_PALETTE(templateIndex(tid), isExpanded);
- } else if (renderInfo.node.type === tf.graph.NodeType.SERIES) {
+ } else if (renderInfo.node.type === NodeType.SERIES) {
// If expanded, we're showing the background rect, which we want to
// appear gray. Otherwise we're showing a stack of ellipses which we
// want to show white.
@@ -587,10 +590,10 @@ export function getFillForNode(templateIndex, colorBy,
* that can't be done in css).
*/
export function stylize(nodeGroup, renderInfo: render.RenderNodeInfo,
- sceneBehavior, nodeClass?) {
+ sceneElement, nodeClass?) {
nodeClass = nodeClass || Class.Node.SHAPE;
- let isHighlighted = sceneBehavior.isNodeHighlighted(renderInfo.node.name);
- let isSelected = sceneBehavior.isNodeSelected(renderInfo.node.name);
+ let isHighlighted = sceneElement.isNodeHighlighted(renderInfo.node.name);
+ let isSelected = sceneElement.isNodeSelected(renderInfo.node.name);
let isExtract = renderInfo.isInExtract || renderInfo.isOutExtract;
let isExpanded = renderInfo.expanded;
nodeGroup.classed("highlighted", isHighlighted);
@@ -601,8 +604,8 @@ export function stylize(nodeGroup, renderInfo: render.RenderNodeInfo,
// Main node always exists here and it will be reached before subscene,
// so d3 selection is fine here.
let node = nodeGroup.select("." + nodeClass + " ." + Class.Node.COLOR_TARGET);
- let fillColor = getFillForNode(sceneBehavior.templateIndex,
- ColorBy[sceneBehavior.colorBy.toUpperCase()],
+ let fillColor = getFillForNode(sceneElement.templateIndex,
+ ColorBy[sceneElement.colorBy.toUpperCase()],
renderInfo, isExpanded);
node.style("fill", fillColor);
@@ -617,7 +620,7 @@ export function stylize(nodeGroup, renderInfo: render.RenderNodeInfo,
export function getStrokeForFill(fill: string) {
// If node is colored by a gradient, then use a dark gray outline.
return fill.substring(0, 3) === "url" ?
- tf.graph.render.MetanodeColors.GRADIENT_OUTLINE :
+ render.MetanodeColors.GRADIENT_OUTLINE :
d3.rgb(fill).darker().toString();
}
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 24c16e31ee..685ad646f7 100644
--- a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/scene.ts
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/scene.ts
@@ -243,12 +243,12 @@ export function selectChild(container, tagName: string, className?: string) {
*
* @param container D3 selection of the parent.
* @param renderNode render node of a metanode or series node.
- * @param sceneBehavior Parent scene module.
+ * @param sceneElement <tf-graph-scene> polymer element.
* @param sceneClass class attribute of the scene (default="scene").
*/
export function buildGroup(container,
renderNode: render.RenderGroupNodeInfo,
- sceneBehavior,
+ sceneElement,
sceneClass: string) {
sceneClass = sceneClass || Class.Scene.GROUP;
let isNewSceneGroup = selectChild(container, "g", sceneClass).empty();
@@ -272,17 +272,17 @@ export function buildGroup(container,
}
// Create the layer of edges for this scene (paths).
- edge.buildGroup(coreGroup, renderNode.coreGraph, sceneBehavior);
+ edge.buildGroup(coreGroup, renderNode.coreGraph, sceneElement);
// Create the layer of nodes for this scene (ellipses, rects etc).
- node.buildGroup(coreGroup, coreNodes, sceneBehavior);
+ node.buildGroup(coreGroup, coreNodes, sceneElement);
// In-extract
if (renderNode.isolatedInExtract.length > 0) {
let inExtractGroup = selectOrCreateChild(sceneGroup, "g",
Class.Scene.INEXTRACT);
node.buildGroup(inExtractGroup, renderNode.isolatedInExtract,
- sceneBehavior);
+ sceneElement);
} else {
selectChild(sceneGroup, "g", Class.Scene.INEXTRACT).remove();
}
@@ -292,7 +292,7 @@ export function buildGroup(container,
let outExtractGroup = selectOrCreateChild(sceneGroup, "g",
Class.Scene.OUTEXTRACT);
node.buildGroup(outExtractGroup, renderNode.isolatedOutExtract,
- sceneBehavior);
+ sceneElement);
} else {
selectChild(sceneGroup, "g", Class.Scene.OUTEXTRACT).remove();
}
@@ -345,9 +345,9 @@ function position(sceneGroup, renderNode: render.RenderGroupNodeInfo) {
};
/** Adds a click listener to a group that fires a graph-select event */
-export function addGraphClickListener(graphGroup, sceneBehavior) {
+export function addGraphClickListener(graphGroup, sceneElement) {
d3.select(graphGroup).on("click", () => {
- sceneBehavior.fire("graph-select");
+ sceneElement.fire("graph-select");
});
};
diff --git a/tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html b/tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html
index 3aac0ca436..cb9abc6cee 100644
--- a/tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html
+++ b/tensorflow/tensorboard/components/tf-graph-info/tf-node-info.html
@@ -176,6 +176,7 @@
class="non-control-list-item"
card-node="[[_node]]"
item-node="[[_getNode(item, graphHierarchy)]]"
+ edge-label="[[_getPredEdgeLabel(item)]]"
item-render-info="[[_getRenderInfo(item, renderHierarchy)]]"
name="[[item]]"
item-type="predecessors"
@@ -226,6 +227,7 @@
class="non-control-list-item"
card-node="[[_node]]"
item-node="[[_getNode(item, graphHierarchy)]]"
+ edge-label="[[_getSuccEdgeLabel(item)]]"
item-render-info="[[_getRenderInfo(item, renderHierarchy)]]"
name="[[item]]"
item-type="successor"
@@ -364,6 +366,31 @@
// long node names wrap cleanly at path boundaries.
return (nodeName || '').replace(/\//g, '<wbr>/');
},
+ _getPredEdgeLabel: function(sourceName) {
+ return this._getEdgeLabel(sourceName, this.nodeName);
+ },
+ _getSuccEdgeLabel: function(destName) {
+ return this._getEdgeLabel(this.nodeName, destName);
+ },
+ _getEdgeLabel: function(sourceName, destName) {
+ if (!this._node) {
+ // The user clicked outside, thus no node is selected and
+ // the info card should be hidden.
+ return;
+ }
+ var parent = this._node.parentNode;
+ var sourceNode = this.graphHierarchy.node(sourceName);
+ if (!sourceNode.isGroupNode) {
+ // Show the tensor shape directly.
+ return tf.graph.scene.edge.getShapeLabelFromNode(sourceNode);
+ }
+ sourceName = this.renderHierarchy.getNearestVisibleAncestor(sourceName);
+ destName = this.renderHierarchy.getNearestVisibleAncestor(destName);
+ var metaedge = parent.metagraph.edge(sourceName, destName) ||
+ parent.bridgegraph.edge(sourceName, destName);
+ return tf.graph.scene.edge.getLabelForEdge(metaedge,
+ this.renderHierarchy);
+ },
_getRenderInfo: function(nodeName, renderHierarchy) {
return this.renderHierarchy.getOrCreateRenderNodeByName(nodeName);
},
diff --git a/tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html b/tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html
index 33035d3d68..8f0f610149 100644
--- a/tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html
+++ b/tensorflow/tensorboard/components/tf-graph-info/tf-node-list-item.html
@@ -9,6 +9,7 @@
font-size: 11pt;
font-weight: 400;
position: relative;
+ display: inline-block;
}
#list-item:hover {
@@ -20,7 +21,6 @@
}
#list-item span {
- display: block;
margin-left: 40px;
}
@@ -28,6 +28,13 @@
color: #999;
}
+ #list-item span.edge-label {
+ float: right;
+ font-size: 10px;
+ margin-left: 3px;
+ margin-right: 5px;
+ }
+
.node-icon {
position: absolute;
top: 1px;
@@ -44,6 +51,7 @@
node="[[itemNode]]" render-info="[[itemRenderInfo]]"
template-index="[[templateIndex]]"></tf-graph-icon>
<span title$="[[name]]">[[name]]</span>
+ <span class="edge-label">[[edgeLabel]]</span>
</div>
</template>
@@ -63,6 +71,8 @@
* @type {tf.graph.Node}
*/
itemNode: Object,
+ /** The edge label associated with this item. */
+ edgeLabel: String,
/**
* The render node information for the item node. Used by the graph
* icon in determining fill color.
@@ -75,7 +85,7 @@
},
colorBy: String,
colorByParams: Object,
- templateIndex: Function,
+ templateIndex: Function
},
_itemTypeChanged: function() {
@@ -94,7 +104,6 @@
type: this.itemType
});
}
-
});
})();
</script>
diff --git a/tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html b/tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html
index 8f1d72cfa9..56d4f90ec4 100644
--- a/tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html
+++ b/tensorflow/tensorboard/components/tf-graph-loader/tf-graph-loader.html
@@ -129,8 +129,10 @@ Polymer({
this._setHasStats(statsJson != null);
this._setOutGraphHierarchy(graphHierarchy);
}.bind(this))
- .catch(function(reason) {
- tracker.reportError("Graph visualization failed: " + reason);
+ .catch(function(e) {
+ // Generic error catch, for errors that happened outside
+ // asynchronous tasks.
+ tracker.reportError("Graph visualization failed: " + e, e);
});
},
_selectedDatasetChanged: function(datasetIndex, datasets) {
diff --git a/tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html b/tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html
index 80118d95fd..2e5ef3d1de 100644
--- a/tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html
+++ b/tensorflow/tensorboard/components/tf-graph/demo/tf-graph-demo.html
@@ -93,6 +93,10 @@ var datasets = [
statsPath: "mnist_train_stats.json"
},
{
+ name: "Mnist Train (with shapes)",
+ path: "mnist_train_shapes.pbtxt",
+ },
+ {
name: "Inception Train (huge)",
path: "inception_train.pbtxt",
},
@@ -117,7 +121,7 @@ var datasets = [
path: "ptb_word_lstm_test_eval.pbtxt",
},
{
- name: "Cifar10 Train",
+ name: "Cifar10 Train (with shapes)",
path: "cifar10_train.pbtxt",
},
{
@@ -166,7 +170,7 @@ Polymer({
},
selectedDataset: {
type: Number,
- value: 1
+ value: 9 // Cifar train with shapes.
},
_progress: Object
},
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph-style.html b/tensorflow/tensorboard/components/tf-graph/tf-graph-style.html
index 3e6f7f2112..e369940976 100644
--- a/tensorflow/tensorboard/components/tf-graph/tf-graph-style.html
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph-style.html
@@ -278,7 +278,6 @@
::content .edge > path.edgeline {
fill: none;
- marker-end: url(#arrowhead);
stroke: #bbb;
stroke-linecap: round;
stroke-width: 0.75;
@@ -288,6 +287,12 @@
marker-start: url(#ref-arrowhead);
}
+/* Labels showing tensor shapes on edges */
+::content .edge > text {
+ font-size: 3.5px;
+ fill: #666;
+}
+
::content #arrowhead {
fill: #bbb;
}
diff --git a/tensorflow/tensorboard/components/tf-graph/tf-graph.html b/tensorflow/tensorboard/components/tf-graph/tf-graph.html
index bbe86cee82..7d38694a9b 100644
--- a/tensorflow/tensorboard/components/tf-graph/tf-graph.html
+++ b/tensorflow/tensorboard/components/tf-graph/tf-graph.html
@@ -214,6 +214,10 @@ Polymer({
this.set('highlightedNode', null);
},
_nodeToggleExpand: function(event) {
+ // Immediately select the node that is about to be expanded.
+ this._nodeSelected(event);
+
+ // Compute the sub-hierarchy scene.
var nodeName = event.detail.name;
var renderNode = this.renderHierarchy.getRenderNodeByName(nodeName);
// Op nodes are not expandable.
@@ -222,9 +226,13 @@ Polymer({
}
this.renderHierarchy.buildSubhierarchy(nodeName);
renderNode.expanded = !renderNode.expanded;
- this.querySelector('#scene').setNodeExpanded(renderNode);
- // Also select the expanded node.
- this._nodeSelected(event);
+
+ // Expand the node with some delay so that the user can immediately see
+ // the visual effect of selecting that node, before the expansion is
+ // done.
+ this.async(function() {
+ this.querySelector('#scene').setNodeExpanded(renderNode);
+ }, 75);
},
_nodeToggleExtract: function(event) {
// Toggle the include setting of the specified node appropriately.