aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/tensorboard/components/tf-graph-common/lib/scene/edge.ts
blob: e11ec97f807c955138b1aed5f5ca6bf95c79b6ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/// <reference path="../graph.ts" />
/// <reference path="../render.ts" />
/// <reference path="scene.ts" />

module tf.graph.scene.edge {

let Scene = tf.graph.scene; // Aliased

export function getEdgeKey(edgeObj) {
  return edgeObj.v + tf.graph.EDGE_KEY_DELIM + edgeObj.w;
}

/**
 * Select or Create a "g.edges" group to a given sceneGroup
 * and builds a number of "g.edge" groups inside the group.
 *
 * Structure Pattern:
 *
 * <g class="edges">
 *   <g class="edge">
 *     <path class="edgeline"/>
 *   </g>
 *   ...
 * </g>
 *
 *
 * @param sceneGroup container
 * @param graph
 * @param sceneBehavior Parent scene module.
 * @return selection of the created nodeGroups
 */
export function buildGroup(sceneGroup,
  graph: graphlib.Graph<tf.graph.render.RenderNodeInformation,
    tf.graph.render.RenderMetaedgeInformation>, sceneBehavior) {
  let edgeData = _.reduce(graph.edges(), (edges, edgeObj) => {
    let edgeLabel = graph.edge(edgeObj);
    edges.push({
      v: edgeObj.v,
      w: edgeObj.w,
      label: edgeLabel
    });
    return edges;
  }, []);

  let container = scene.selectOrCreateChild(sceneGroup, "g",
     Class.Edge.CONTAINER);
  let containerNode = container.node();

  // Select all children and join with data.
  // (Note that all children of g.edges are g.edge)
  let edgeGroups = container.selectAll(function() {
    // using d3's selector function
    // 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);

  // Make edges a group to support rendering multiple lines for metaedge
  edgeGroups.enter()
    .append("g")
    .attr("class", Class.Edge.GROUP)
    .attr("data-edge", getEdgeKey)
    .each(function(d) {
      let edgeGroup = d3.select(this);
      d.label.edgeGroup = edgeGroup;
      // index node group for quick highlighting
      sceneBehavior._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, scene, extraEdgeClass);
    });

  edgeGroups.each(position);
  edgeGroups.each(function(d) {
    stylize(d3.select(this), d, sceneBehavior);
  });

  edgeGroups.exit()
    .each(d => {
      delete sceneBehavior._edgeGroupIndex[getEdgeKey(d)];
    })
    .remove();
  return edgeGroups;
};

/**
 * For a given d3 selection and data object, create a path to represent the
 * edge described in d.label.
 *
 * If d.label is defined, it will be a RenderMetaedgeInformation instance. It
 * 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, sceneBehavior, edgeClass?) {
  edgeClass = edgeClass || Class.Edge.LINE; // set default type

  if (d.label && d.label.structural) {
    edgeClass += " " + Class.Edge.STRUCTURAL;
  }

  edgeGroup.append("path")
    .attr("class", edgeClass);
};

/**
 * Returns a tween interpolator for the endpoint of an edge path.
 */
function getEdgePathInterpolator(d, i, a) {
  let renderMetaedgeInfo = d.label;
  let adjoiningMetaedge = renderMetaedgeInfo.adjoiningMetaedge;
  if (!adjoiningMetaedge) {
    return d3.interpolate(a, interpolate(renderMetaedgeInfo.points));
  }

  let renderPath = this;

  // Get the adjoining path that matches the adjoining metaedge.
  let adjoiningPath =
    <SVGPathElement>((<HTMLElement>adjoiningMetaedge.edgeGroup.node())
      .firstChild);

  // Find the desired SVGPoint along the adjoining path, then convert those
  // coordinates into the space of the renderPath using its Current
  // Transformation Matrix (CTM).
  let inbound = renderMetaedgeInfo.metaedge.inbound;

  return function(t) {
    let adjoiningPoint = adjoiningPath
      .getPointAtLength(inbound ? adjoiningPath.getTotalLength() : 0)
      .matrixTransform(adjoiningPath.getCTM())
      .matrixTransform(renderPath.getCTM().inverse());

    // 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;
    let dPath = interpolate(points);
    return dPath;
  };
}

export let interpolate = d3.svg.line()
  .interpolate("basis")
  .x((d: any) => { return d.x; })
  .y((d: any) => { return d.y; });

function position(d) {
  d3.select(this).select("path." + Class.Edge.LINE)
    .each(function(d) {
      let path = d3.select(this);
      path.transition().attrTween("d", getEdgePathInterpolator);
    });
};

/**
 * For a given d3 selection and data object, mark the edge as a control
 * dependency if it contains only control edges.
 *
 * d's label property will be a RenderMetaedgeInformation object.
 */
function stylize(edgeGroup, d, stylize) {
  let a;
  let metaedge = d.label.metaedge;
  edgeGroup
    .select("path." + Class.Edge.LINE)
    .classed("control-dep", metaedge && !metaedge.numRegularEdges);
};

} // close module