///
///
///
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:
*
*
*
*
*
* ...
*
*
*
* @param sceneGroup container
* @param graph
* @param sceneBehavior Parent scene module.
* @return selection of the created nodeGroups
*/
export function buildGroup(sceneGroup,
graph: graphlib.Graph, 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 =
((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