Created
August 9, 2013 21:03
-
-
Save anonymous/6197173 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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