Fix arrow marker for thick reference edges.

- Added several fixed-sized arrow markers for reference edges (small, medium, large, xlarge)
- Added a method to shorten the path enough such that the tip of the start/end marker will
  point to the start/end of the path. It needs an arbitrary marker.
- Updated the legend to reflect that only ref edges have an arrow.
- Removed unused css related to this.
Change: 118530630
This commit is contained in:
Dan Smilkov 2016-03-29 17:48:02 -08:00 committed by TensorFlower Gardener
parent 972d732789
commit e3a0d6fb61
4 changed files with 121 additions and 50 deletions
tensorflow/tensorboard/components

View File

@ -74,18 +74,6 @@ 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.
*/
@ -169,7 +157,6 @@ 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
@ -185,12 +172,6 @@ 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(),
@ -1094,6 +1075,12 @@ export class RenderMetaedgeInfo {
*/
edgeGroup: d3.Selection<RenderMetaedgeInfo>;
/** Id of the <marker> used as a start-marker for the edge path. */
startMarkerId: string;
/** Id of the <marker> used as an end-marker for the edge path. */
endMarkerId: string;
constructor(metaedge: Metaedge) {
this.metaedge = metaedge;
this.adjoiningMetaedge = null;

View File

@ -17,6 +17,28 @@ module tf.graph.scene.edge {
/** Delimiter between dimensions when showing sizes of tensors. */
const TENSOR_SHAPE_DELIM = "×";
/** 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];
let edgeWidthScale = d3.scale.pow()
.exponent(EDGE_WIDTH_SCALE_EXPONENT)
.domain(DOMAIN_EDGE_WIDTH_SCALE)
.range([MIN_EDGE_WIDTH, MAX_EDGE_WIDTH])
.clamp(true);
let arrowheadMap = d3.scale.quantize()
.domain([MIN_EDGE_WIDTH, MAX_EDGE_WIDTH])
.range(["small", "medium", "large", "xlarge"]);
export type EdgeData = {v: string, w: string, label: render.RenderMetaedgeInfo};
export function getEdgeKey(edgeObj: EdgeData) {
@ -79,13 +101,9 @@ export function buildGroup(sceneGroup,
// index node group for quick highlighting
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
? Class.Edge.REF_LINE + " " + Class.Edge.LINE
: undefined;
// Add line during enter because we're assuming that type of line
// normally does not change.
appendEdge(edgeGroup, d, sceneElement, extraEdgeClass);
appendEdge(edgeGroup, d, sceneElement);
});
edgeGroups.each(position);
@ -136,6 +154,54 @@ export function getLabelForEdge(metaedge: Metaedge,
}
}
/**
* Shortens the path enought such that the tip of the start/end marker will
* point to the start/end of the path. The marker can be of arbitrary size.
*
* @param points Array of path control points.
* @param marker D3 selection of the <marker> svg element.
* @param isStart Is the marker a `start-marker`. If false, the marker is
* an `end-marker`.
* @return The new array of control points.
*/
function adjustPathPointsForMarker(points: render.Point[],
marker: d3.Selection<any>, isStart: boolean): render.Point[] {
let lineFunc = d3.svg.line<render.Point>()
.x(d => d.x)
.y(d => d.y);
let path = d3.select(
document.createElementNS("http://www.w3.org/2000/svg", "path")
).attr("d", lineFunc(points));
let markerWidth = +marker.attr("markerWidth");
let viewBox = marker.attr("viewBox").split(" ").map(Number);
let viewBoxWidth = viewBox[2] - viewBox[0];
let refX = +marker.attr("refX");
let pathNode = <SVGPathElement> path.node();
if (isStart) {
let fractionStickingOut = refX / viewBoxWidth;
let length = markerWidth * fractionStickingOut;
let point = pathNode.getPointAtLength(length);
// Figure out how many segments of the path we need to remove in order
// to shorten the path.
let segIndex = pathNode.getPathSegAtLength(length);
// Update the very first segment.
points[segIndex - 1] = {x: point.x, y: point.y};
// Ignore every point before segIndex - 1.
return points.slice(segIndex - 1);
} else {
let fractionStickingOut = 1 - refX / viewBoxWidth;
let length = pathNode.getTotalLength() - markerWidth * fractionStickingOut;
let point = pathNode.getPointAtLength(length);
// Figure out how many segments of the path we need to remove in order
// to shorten the path.
let segIndex = pathNode.getPathSegAtLength(length);
// Update the very last segment.
points[segIndex] = {x: point.x, y: point.y};
// Ignore every point after segIndex.
return points.slice(0, segIndex + 1);
}
}
/**
* For a given d3 selection and data object, create a path to represent the
* edge described in d.label.
@ -146,7 +212,7 @@ export function getLabelForEdge(metaedge: Metaedge,
*/
export function appendEdge(edgeGroup, d: EdgeData,
sceneElement: {renderHierarchy: render.RenderGraphInfo},
edgeClass: string) {
edgeClass?: string) {
let size = 1;
if (d.label != null && d.label.metaedge != null) {
// There is an underlying Metaedge.
@ -160,9 +226,9 @@ export function appendEdge(edgeGroup, d: EdgeData,
// 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);
let strokeWidth = edgeWidthScale(size);
edgeGroup.append("path")
let path = edgeGroup.append("path")
.attr({
"id": pathId,
"class": edgeClass,
@ -170,6 +236,13 @@ export function appendEdge(edgeGroup, d: EdgeData,
"stroke-width": strokeWidth + "px"
});
// Check if there is a reference edge and add an arrowhead of the right size.
if (d.label && d.label.metaedge && d.label.metaedge.numRefEdges) {
let markerId = `ref-arrowhead-${arrowheadMap(strokeWidth)}`;
path.style("marker-start", `url(#${markerId})`);
d.label.startMarkerId = markerId;
}
if (d.label == null || d.label.metaedge == null) {
// There is no associated metaedge, thus no text.
// This happens for annotation edges.
@ -201,6 +274,18 @@ function getEdgePathInterpolator(d: EdgeData, i: number, a: string) {
let renderMetaedgeInfo = <render.RenderMetaedgeInfo> d.label;
let adjoiningMetaedge = renderMetaedgeInfo.adjoiningMetaedge;
let points = renderMetaedgeInfo.points;
// Adjust the path so that start/end markers point to the end
// of the path.
if (d.label.startMarkerId) {
points = adjustPathPointsForMarker(points,
d3.select("#" + d.label.startMarkerId), true);
}
if (d.label.endMarkerId) {
points = adjustPathPointsForMarker(points,
d3.select("#" + d.label.endMarkerId), false);
}
if (!adjoiningMetaedge) {
return d3.interpolate(a, interpolate(points));
}

View File

@ -317,16 +317,12 @@ svg.icon {
<svg class="icon" height="15px"
preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
<defs>
<marker id="arrowhead-legend" fill="#bbb" 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-legend" fill="#bbb" 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>
</defs>
<path marker-end="url(#arrowhead-legend)" stroke="#bbb"
<path stroke="#bbb"
d="M2 9 l 23 0" stroke-linecap="round" />
</svg>
</td>
@ -336,7 +332,7 @@ svg.icon {
<td>
<svg class="icon" height="15px"
preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
<path marker-end="url(#arrowhead-legend)" stroke="#bbb"
<path stroke="#bbb"
d="M2 9 l 23 0" stroke-linecap="round" stroke-dasharray="2, 2" />
</svg>
</td>
@ -347,7 +343,7 @@ svg.icon {
<svg class="icon" height="15px"
preserveAspectRatio="xMinYMid meet" viewBox="0 0 15 15">
<path marker-start="url(#ref-arrowhead-legend)"
marker-end="url(#arrowhead-legend)" stroke="#bbb" d="M2 9 l 23 0"
stroke="#bbb" d="M2 9 l 23 0"
stroke-linecap="round" />
</svg>
</td>

View File

@ -289,21 +289,13 @@
stroke-width: 0.75;
}
::content .edge > path.edgeline.refline {
marker-start: url(#ref-arrowhead);
}
/* Labels showing tensor shapes on edges */
::content .edge > text {
font-size: 3.5px;
fill: #666;
}
::content #arrowhead {
fill: #bbb;
}
::content #ref-arrowhead {
::content .ref-arrowhead {
fill: #bbb;
}
@ -370,15 +362,26 @@
</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"/>
<!-- Arrow heads for edge paths of different predefined sizes. -->
<path id="ref-arrowhead-path" d="M 10,0 L 0,5 L 10,10 C 7,7 7,3 10,0"/>
<marker class="ref-arrowhead" id="ref-arrowhead-small" viewBox="0 0 10 10" markerWidth="10" markerHeight="10"
refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
<use xlink:href="#ref-arrowhead-path" />
</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 class="ref-arrowhead" id="ref-arrowhead-medium" viewBox="0 0 10 10" markerWidth="13" markerHeight="13"
refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
<use xlink:href="#ref-arrowhead-path" />
</marker>
<marker class="ref-arrowhead" id="ref-arrowhead-large" viewBox="0 0 10 10" markerWidth="16" markerHeight="16"
refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
<use xlink:href="#ref-arrowhead-path" />
</marker>
<marker class="ref-arrowhead" id="ref-arrowhead-xlarge" viewBox="0 0 10 10" markerWidth="20" markerHeight="20"
refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
<use xlink:href="#ref-arrowhead-path" />
</marker>
<!-- Arrow head for annotation edge paths. -->
<marker id="annotation-arrowhead" markerWidth="5" markerHeight="5"
refX="5" refY="2.5" orient="auto">