Skip to content

Instantly share code, notes, and snippets.

@mahmoud
Forked from anonymous/index.html
Created August 9, 2013 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mahmoud/6197317 to your computer and use it in GitHub Desktop.
Save mahmoud/6197317 to your computer and use it in GitHub Desktop.
<!doctype html>
<meta charset="utf-8">
<title>C3 visualization</title>
<style>
text {
font: 300 16px "Helvetica Neue";
}
rect {
fill: #fff;
}
.node > rect {
stroke-width: 3px;
stroke: #333;
fill: none;
}
.node:hover {
cursor: pointer;
opacity: 0.4;
}
.edge rect {
fill: #fff
}
.edge path {
fill: none;
stroke: #333;
stroke-width: 1.5px;
}
.edge:hover {
cursor: pointer;
opacity: 0.4;
}
.cp {
opacity: 0;
}
.cp:hover {
cursor:pointer;
opacity: 1;
}
</style>
<style>
h1, h2 {
color: #333;
}
textarea {
width: 800px;
}
label {
margin-top: 1em;
display: block;
}
.error {
color: red;
}
svg {
border: 1px solid #999;
}
</style>
<h1>Dagre Interactive Demo</h1>
<h2>Class Hierarchy</h2>
<h3>(Red lines are the linearization</h3>
<button onClick="(function (arguments) { draw(d_nodes, d_edges); return false; })()">C3!</button>
<br>
<svg width=800 height=600>
<defs>
<marker id="arrowhead"
viewBox="0 0 10 10"
refX="8"
refY="5"
markerUnits="strokeWidth"
markerWidth="8"
markerHeight="5"
orient="auto"
style="fill: #333">
<path d="M 0 0 L 10 5 L 0 10 z"></path>
</marker>
</defs>
</svg>
<script src="http://d3js.org/d3.v2.min.js"></script>
<script src="http://cpettitt.github.io/project/dagre/latest/dagre.js"></script>
<script>var InvalidMRO = {};
Array.prototype.last = function () {
return this.slice(-1)[0];
};
var rest = function (a) {
return a.slice(1);
};
var merge = function (classes_list) {
var merged = [], i;
while (classes_list.length) {
var valid = classes_list.some(
function (classes) {
var head = classes[0],
tails = classes_list.map(rest);
if (tails.every(function (tail) { return tail.indexOf(head) < 0; })) {
if ((head !== undefined) && (head !== merged.last())) {
merged.push(head);
}
classes_list.forEach(function (classes) {
var idx;
if ((idx = classes.indexOf(head)) > -1)
classes.splice(idx, 1);
if (!classes.length)
classes_list.splice(classes_list.indexOf(classes), 1);
});
return true;
}
return false;
});
if (!valid)
throw InvalidMRO;
}
return merged;
};
var c3 = function (cls) {
var linearized = cls.bases.map(c3), result;
if (cls.bases)
linearized.push(cls.bases.slice(0));
result = merge(linearized);
result.unshift(cls);
return result;
};
</script>
<script>
var svg = d3.select("svg");
var svgGroup = svg.append("g").attr("transform", "translate(5, 5)");
var nodes, edges, markers = {};
function colorized_arrowhead(color) {
var arrowhead_id = "arrowhead-" + color;
if (markers[color] === undefined) {
var defs = document.getElementsByTagName("defs")[0];
var new_marker = document.getElementById("arrowhead").cloneNode(true);
new_marker.setAttribute("id", arrowhead_id);
new_marker.setAttribute("style", "fill:" + color +";");
defs.appendChild(new_marker);
markers[color] = true;
}
return arrowhead_id;
}
function draw(nodeData, edgeData) {
// D3 doesn't appear to like rebinding with the same id but a new object,
// so for now we remove everything.
nodeData.forEach(function (node) {
node.inEdges = [];
node.outEdges = [];
});
edgeData.forEach(function (edge) {
edge.source.outEdges.push(edge);
edge.target.inEdges.push(edge);
});
svgGroup.selectAll("*").remove();
nodes = svgGroup
.selectAll("g .node")
.data(nodeData, function(d) { return d.id; });
var nodeEnter = nodes
.enter()
.append("g")
.attr("class", "node")
.attr("id", function(d) { return "node-" + d.id; })
.each(function(d) { d.nodePadding = 10; });
nodeEnter.append("rect");
addLabels(nodeEnter);
nodes.exit().remove();
edges = svgGroup
.selectAll("g .edge")
.data(edgeData, function(d) { return d.id; });
var edgeEnter = edges
.enter()
.append("g")
.attr("class", "edge")
.attr("id", function(d) { return "edge-" + d.id; })
.each(function(d) { d.nodePadding = 0; });
edgeEnter
.append("path")
.attr("marker-end", function (d) {
var arrowhead = (d.color === undefined) ? "arrowhead" : colorized_arrowhead(d.color);
return "url(#" + arrowhead + ")"; })
.attr("style", function (d) { return "stroke:" + (d.color || "black") + ";"; });
addLabels(edgeEnter);
edges.exit().remove();
recalcLabels();
// Add zoom behavior to the SVG canvas
svg.call(d3.behavior.zoom().on("zoom", function redraw() {
svgGroup.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}));
// Run the actual layout
dagre.layout()
.nodes(nodeData)
.edges(edgeData)
.debugLevel(2)
.run();
// Ensure that we have at least two points between source and target
edges.each(function(d) { ensureTwoControlPoints(d); });
nodes.call(d3.behavior.drag()
.origin(function(d) { return {x: d.dagre.x, y: d.dagre.y}; })
.on('drag', function (d, i) {
d.dagre.x = d3.event.x;
d.dagre.y = d3.event.y;
d.outEdges.forEach(function(e) {
var points = e.dagre.points;
if (points[0].y === points[1].y) {
points[1].y += d3.event.dy;
}
points[0].y += d3.event.dy;
if (points[1].y < points[0].y) {
points[0].y = points[1].y;
}
translateEdge(e, d3.event.dx, 0);
});
d.inEdges.forEach(function(e) {
var points = e.dagre.points;
if (points[1].y === points[0].y) {
points[0].y += d3.event.dy;
}
points[1].y += d3.event.dy;
if (points[0].y > points[1].y) {
points[1].y = points[0].y;
}
translateEdge(e, d3.event.dx, 0);
});
update();
}));
edges
.call(d3.behavior.drag()
.on('drag', function (d, i) {
translateEdge(d, d3.event.dx, d3.event.dy);
update();
}));
edgeEnter
.selectAll("circle.cp")
.data(function(d) {
d.dagre.points.forEach(function(p) { p.parent = d; });
return d.dagre.points.slice(0).reverse();
})
.enter()
.append("circle")
.attr("class", "cp")
.call(d3.behavior.drag()
.on("drag", function(d) {
d.y += d3.event.dy;
translateEdge(d.parent, d3.event.dx, 0);
update();
}));
// Re-render
update();
}
function addLabels(selection) {
var labelGroup = selection
.append("g")
.attr("class", "label");
labelGroup.append("rect");
var foLabel = labelGroup
.filter(function(d) { return d.label[0] === "<"; })
.append("foreignObject")
.attr("class", "htmllabel");
foLabel
.append("xhtml:div")
.style("float", "left");
labelGroup
.filter(function(d) { return d.label[0] !== "<"; })
.append("text");
}
function recalcLabels() {
var labelGroup = svgGroup.selectAll("g.label");
var foLabel = labelGroup
.selectAll(".htmllabel")
// TODO find a better way to get the dimensions for foriegnObjects
.attr("width", "100000");
foLabel
.select("div")
.html(function(d) { return d.label; })
.each(function(d) {
d.width = this.clientWidth;
d.height = this.clientHeight;
d.nodePadding = 0;
});
foLabel
.attr("width", function(d) { return d.width; })
.attr("height", function(d) { return d.height; });
var textLabel = labelGroup
.filter(function(d) { return d.label[0] !== "<"; });
textLabel
.select("text")
.attr("text-anchor", "left")
.append("tspan")
.attr("dy", "1em")
.text(function(d) { return d.label; });
labelGroup
.each(function(d) {
var bbox = this.getBBox();
d.bbox = bbox;
if (d.label.length) {
d.width = bbox.width + 2 * d.nodePadding;
d.height = bbox.height + 2 * d.nodePadding;
} else {
d.width = d.height = 0;
}
});
}
function ensureTwoControlPoints(d) {
var points = d.dagre.points;
if (!points.length) {
var s = e.source.dagre;
var t = e.target.dagre;
points.push({ x: Math.abs(s.x - t.x) / 2, y: Math.abs(s.y + t.y) / 2 });
}
if (points.length === 1) {
points.push({ x: points[0].x, y: points[0].y });
}
}
// Translates all points in the edge using `dx` and `dy`.
function translateEdge(e, dx, dy) {
e.dagre.points.forEach(function(p) {
p.x += dx;
p.y += dy;
});
}
function update() {
nodes
.attr("transform", function(d) {
return "translate(" + d.dagre.x + "," + d.dagre.y +")"; })
.selectAll("g.node rect")
.attr("x", function(d) { return -(d.bbox.width / 2 + d.nodePadding); })
.attr("y", function(d) { return -(d.bbox.height / 2 + d.nodePadding); })
.attr("width", function(d) { return d.width; })
.attr("height", function(d) { return d.height; });
edges
.selectAll("path")
.attr("d", function(d) {
var points = d.dagre.points.slice(0);
var source = dagre.util.intersectRect(d.source.dagre, points[0]);
var target = dagre.util.intersectRect(d.target.dagre, points[points.length - 1]);
points.unshift(source);
points.push(target);
return d3.svg.line()
.x(function(e) { return e.x; })
.y(function(e) { return e.y; })
.interpolate("linear")
(points);
});
edges
.selectAll("circle")
.attr("r", 5)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svgGroup
.selectAll("g.label rect")
.attr("x", function(d) { return -d.nodePadding; })
.attr("y", function(d) { return -d.nodePadding; })
.attr("width", function(d) { return d.width; })
.attr("height", function(d) { return d.height; });
nodes
.selectAll("g.label")
.attr("transform", function(d) { return "translate(" + (-d.bbox.width / 2) + "," + (-d.bbox.height / 2) + ")"; });
edges
.selectAll("g.label")
.attr("transform", function(d) {
var points = d.dagre.points;
var x = (points[0].x + points[1].x) / 2;
var y = (points[0].y + points[1].y) / 2;
return "translate(" + (-d.bbox.width / 2 + x) + "," + (-d.bbox.height / 2 + y) + ")";
});
}
function findNodeForClass(cls, nodes) {
var node;
if (!nodes.some(function (n) {
return (n && (n.label === cls.name)) ? (node = n) : false;
}))
return null;
return node;
}
function classToNodeEdges(cls, nodes, edges, color) {
var node = {id: cls.name, label: cls.name};
var edgeColor = color || "black";
var bases_edges = cls.bases.map(function (base) {
var baseNode;
if (!(baseNode = findNodeForClass(base, nodes)))
return null;
return {id: base.name + "-" + cls.name + "-0",
source: baseNode,
target: node,
color: edgeColor,
label: ''};
});
nodes.push(node);
edges.push.apply(edges, bases_edges);
}
function drawPath(classes, nodes, edges) {
var i, prev = findNodeForClass(classes[0], nodes), cur;
for (i = 1; i < classes.length; i++) {
cur = findNodeForClass(classes[i], nodes);
var edge = {id: prev.id + "-" + cur.id + "-1",
source: prev,
target: cur,
color: "red",
label: ""};
prev = cur;
edges.push(edge);
}
}
function renderTest() {
O = {'name': 'O', 'bases': []};
F = {'name': 'F', 'bases': [O]};
E = {'name': 'E', 'bases': [O]};
D = {'name': 'D', 'bases': [O]};
C = {'name': 'C', 'bases': [D, F]};
B = {'name': 'B', 'bases': [D, E]};
A = {'name': 'A', 'bases': [B, C]};
d_nodes = [], d_edges = [];
[O, F, E, D, C, B, A].forEach(function (cls) {
classToNodeEdges(cls, d_nodes, d_edges);
});
draw(d_nodes, d_edges);
}
</script>
<script>
renderTest();
drawPath(c3(A), d_nodes, d_edges);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment