///
///
///
///
module tf.graph.scene.annotation {
/**
* Populate a given annotation container group
*
*
*
* with annotation group of the following structure:
*
*
*
*
*
*
*
* @param container selection of the container.
* @param annotationData node.{in|out}Annotations
* @param d node to build group for.
* @param sceneBehavior polymer scene element.
* @return selection of appended objects
*/
export function buildGroup(container, annotationData: render.AnnotationList,
d: render.RenderNodeInformation, sceneBehavior) {
// Select all children and join with data.
let annotationGroups = 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(annotationData.list, d => { return d.node.name; });
annotationGroups.enter()
.append("g")
.attr("data-name", a => { return a.node.name; })
.each(function(a) {
let aGroup = d3.select(this);
// Add annotation to the index in the scene
sceneBehavior.addAnnotationGroup(a, d, aGroup);
// Append annotation edge
let edgeType = Class.Annotation.EDGE;
let metaedge = a.renderMetaedgeInfo && a.renderMetaedgeInfo.metaedge;
if (metaedge && !metaedge.numRegularEdges) {
edgeType += " " + Class.Annotation.CONTROL_EDGE;
}
// If any edges are reference edges, add the reference edge class.
if (metaedge && metaedge.numRefEdges) {
edgeType += " " + Class.Edge.REF_LINE;
}
edge.appendEdge(aGroup, a, sceneBehavior, edgeType);
if (a.annotationType !== tf.graph.render.AnnotationType.ELLIPSIS) {
addAnnotationLabelFromNode(aGroup, a);
buildShape(aGroup, a, sceneBehavior);
} else {
addAnnotationLabel(aGroup, a.node.name, a, Class.Annotation.ELLIPSIS);
}
});
annotationGroups
.attr("class", a => {
return Class.Annotation.GROUP + " " +
annotationToClassName(a.annotationType) +
" " + node.nodeClass(a);
})
.each(function(a) {
let aGroup = d3.select(this);
update(aGroup, d, a, sceneBehavior);
if (a.annotationType !== tf.graph.render.AnnotationType.ELLIPSIS) {
addInteraction(aGroup, d, sceneBehavior);
}
});
annotationGroups.exit()
.each(function(a) {
let aGroup = d3.select(this);
// Remove annotation from the index in the scene
sceneBehavior.removeAnnotationGroup(a, d, aGroup);
})
.remove();
return annotationGroups;
};
/**
* Maps an annotation enum to a class name used in css rules.
*/
function annotationToClassName(annotationType: render.AnnotationType) {
return (tf.graph.render.AnnotationType[annotationType] || "")
.toLowerCase() || null;
}
function buildShape(aGroup, a: render.Annotation, sceneBehavior) {
if (a.annotationType === tf.graph.render.AnnotationType.SUMMARY) {
let image = scene.selectOrCreateChild(aGroup, "image");
image.attr({
"xlink:href": sceneBehavior.resolveUrl("../../lib/svg/summary-icon.svg"),
"height": "12px",
"width": "12px",
"cursor": "pointer"
});
} 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);
}
}
function addAnnotationLabelFromNode(aGroup, a: render.Annotation) {
let namePath = a.node.name.split("/");
let text = namePath[namePath.length - 1];
let shortenedText = text.length > 8 ? text.substring(0, 8) + "..." : text;
return addAnnotationLabel(aGroup, shortenedText, a, null, text);
}
function addAnnotationLabel(aGroup, label, a, additionalClassNames,
fullLabel?) {
let classNames = Class.Annotation.LABEL;
if (additionalClassNames) {
classNames += " " + additionalClassNames;
}
let titleText = fullLabel ? fullLabel : label;
return aGroup.append("text")
.attr("class", classNames)
.attr("dy", ".35em")
.attr("text-anchor", a.isIn ? "end" : "start")
.text(label)
.append("title").text(titleText);
}
function addInteraction(selection, d: render.RenderNodeInformation,
sceneBehavior) {
selection
.on("mouseover", a => {
sceneBehavior.fire("annotation-highlight", {
name: a.node.name,
hostName: d.node.name
});
})
.on("mouseout", a => {
sceneBehavior.fire("annotation-unhighlight", {
name: a.node.name,
hostName: d.node.name
});
})
.on("click", a => {
// Stop this event"s propagation so that it isn't also considered a
// graph-select.
(d3.event).stopPropagation();
sceneBehavior.fire("annotation-select", {
name: a.node.name,
hostName: d.node.name
});
});
};
/**
* Adjust annotation's position.
*
* @param aGroup selection of a "g.annotation" element.
* @param d Host node data.
* @param a annotation node data.
* @param scene Polymer scene element.
*/
function update(aGroup, d: render.RenderNodeInformation, a: render.Annotation,
sceneBehavior) {
// 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,
Class.Annotation.NODE);
}
if (a.annotationType === tf.graph.render.AnnotationType.SUMMARY) {
// Update the width of the annotation to give space for the image.
a.width += 10;
}
// label position
aGroup.select("text." + Class.Annotation.LABEL).transition().attr({
x: d.x + a.dx + (a.isIn ? -1 : 1) * (a.width / 2 + a.labelOffset),
y: d.y + a.dy
});
// Some annotations (such as summary) are represented using a 12x12 image tag.
// Purposely ommited units (e.g. pixels) since the images are vector graphics.
// If there is an image, we adjust the location of the image to be vertically
// centered with the node and horizontally centered between the arrow and the
// text label.
aGroup.select("image").transition().attr({
x: d.x + a.dx - 3,
y: d.y + a.dy - 6
});
// Node position (only one of the shape selection will be non-empty.)
scene.positionEllipse(aGroup.select("." + Class.Annotation.NODE + " ellipse"),
d.x + a.dx, d.y + a.dy, a.width, a.height);
scene.positionRect(aGroup.select("." + Class.Annotation.NODE + " rect"),
d.x + a.dx, d.y + a.dy, a.width, a.height);
scene.positionRect(aGroup.select("." + Class.Annotation.NODE + " use"),
d.x + a.dx, d.y + a.dy, a.width, a.height);
// Edge position
aGroup.select("path." + Class.Annotation.EDGE).transition().attr("d", a => {
// map relative position to absolute position
let points = a.points.map(p => {
return {x: p.dx + d.x, y: p.dy + d.y};
});
return edge.interpolate(points);
});
};
} // close module