Created
March 28, 2014 19:54
-
-
Save alienrobotwizard/9841692 to your computer and use it in GitHub Desktop.
This file contains 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> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
</head> | |
<style> | |
svg { | |
overflow: hidden; | |
} | |
text { | |
font-weight: 300; | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; | |
font-size: 12px; | |
} | |
.node rect { | |
stroke-width: 1px; | |
stroke: #333; | |
fill: #fff; | |
} | |
.edgePath path { | |
stroke: #333; | |
stroke-width: 1.5px; | |
fill: none; | |
} | |
</style> | |
<body> | |
<!-- <script src="js/d3.v3.min.js"></script> --> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<!-- <script src="js/graphlib.min.js"></script> --> | |
<script src="http://cpettitt.github.io/project/graphlib/latest/graphlib.min.js"></script> | |
<!-- <script src="js/dagre-d3.js"></script> --> | |
<script src="http://cpettitt.github.io/project/dagre-d3/latest/dagre-d3.min.js"></script> | |
<!-- <script src="js/visualization.js"></script> --> | |
<script> | |
var width = 1960; | |
var height = 2700; | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 4 + ")scale(0.95)") | |
.append("g"); | |
var margin = 20; | |
var g = new graphlib.CDigraph(); | |
var layout = dagreD3.layout(); | |
var renderer = new dagreD3.Renderer().layout(layout); | |
g.addNode("G1", { label: "G1"}); | |
g.addNode("A", { label: "A" }); | |
g.addNode("B", { label: "B" }); | |
g.addNode("C", { label: "C" }); | |
g.parent("A", "G1"); | |
g.parent("B", "G1"); | |
g.parent("C", "G1"); | |
g.addNode("G2", { label: "G2"}); | |
g.addNode("D", { label: "D" }); | |
g.addNode("E", { label: "E" }); | |
g.addNode("F", { label: "F" }); | |
g.parent("D", "G2"); | |
g.parent("E", "G2"); | |
g.parent("F", "G2"); | |
g.addNode("G3", { label: "G3"}); | |
g.addNode("G", { label: "G" }); | |
g.addNode("H", { label: "H" }); | |
g.addNode("I", { label: "I" }); | |
g.parent("G", "G3"); | |
g.parent("H", "G3"); | |
g.parent("I", "G3"); | |
g.addNode("G4", { label: "G4"}); | |
g.addNode("J", { label: "J" }); | |
g.addNode("K", { label: "K" }); | |
g.addNode("L", { label: "L" }); | |
g.addNode("M", { label: "M" }); | |
g.parent("J", "G4"); | |
g.parent("K", "G4"); | |
g.parent("L", "G4"); | |
g.parent("M", "G4"); | |
g.addEdge(null, "A", "B", {}); | |
g.addEdge(null, "A", "C", {}); | |
g.addEdge(null, "B", "C", {}); | |
g.addEdge(null, "C", "E", {}); | |
g.addEdge(null, "D", "F", {}); | |
g.addEdge(null, "E", "F", {}); | |
g.addEdge(null, "F", "G", {}); | |
g.addEdge(null, "F", "H", {}); | |
g.addEdge(null, "H", "I", {}); | |
g.addEdge(null, "A", "I", {}); | |
g.addEdge(null, "D", "H", {}); | |
g.addEdge(null, "E", "J", {}); | |
g.addEdge(null, "C", "M", {}); | |
g.addEdge(null, "G", "L", {}); | |
g.addEdge(null, "D", "J", {}); | |
g.addEdge(null, "B", "G", {}); | |
var oldPostRender = renderer.postRender(); | |
function isComposite(graph, u) { | |
return 'children' in graph && graph.children(u).length; | |
} | |
function addLabel(node, root, marginX, marginY) { | |
// Add the rect first so that it appears behind the label | |
var label = node.label; | |
var rect = root.append('rect'); | |
var labelSvg = root.append('g'); | |
addTextLabel(label, | |
labelSvg, | |
Math.floor(node.labelCols), | |
node.labelCut); | |
var bbox = root.node().getBBox(); | |
labelSvg.attr('transform', | |
'translate(' + (-bbox.width / 2) + ',' + (-bbox.height / 2) + ')'); | |
rect | |
.attr('rx', 5) | |
.attr('ry', 5) | |
.attr('x', -(bbox.width / 2 + marginX)) | |
.attr('y', -(bbox.height / 2 + marginY)) | |
.attr('width', bbox.width + 2 * marginX) | |
.attr('height', bbox.height + 2 * marginY); | |
} | |
function addTextLabel(label, root, labelCols, labelCut) { | |
if (labelCut === undefined) labelCut = 'false'; | |
labelCut = (labelCut.toString().toLowerCase() === 'true'); | |
var node = root | |
.append('text') | |
.attr('text-anchor', 'left'); | |
label = label.replace(/\\n/g, '\n'); | |
var arr = labelCols ? wordwrap(label, labelCols, labelCut) : label; | |
arr = arr.split('\n'); | |
for (var i = 0; i < arr.length; i++) { | |
node | |
.append('tspan') | |
.attr('dy', '1em') | |
.attr('x', '1') | |
.text(arr[i]); | |
} | |
} | |
function wordwrap (str, width, cut, brk) { | |
brk = brk || '\n'; | |
width = width || 75; | |
cut = cut || false; | |
if (!str) { return str; } | |
var regex = '.{1,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)'); | |
return str.match( new RegExp(regex, 'g') ).join( brk ); | |
} | |
renderer.drawNodes(function (graph, root) { | |
var subGraphs = graph.children(null); | |
var nodeGroups = root.selectAll('g.cluster') | |
.data(subGraphs, function (sg) { return sg; }); | |
// Remove any existing cluster data | |
nodeGroups.selectAll('*').remove(); | |
nodeGroups | |
.enter() | |
.append('g') | |
.classed('cluster', true); | |
// Draw the nodes for each subgraph inside a group element | |
nodeGroups.each(function (sg) { | |
var nodes = graph.nodes().filter(function(u) { return !isComposite(graph, u) && graph.parent(u) === sg; }); | |
var cluster = d3.select(this); | |
var svgNodes = cluster | |
.selectAll('g.node') | |
.classed('enter', false) | |
.data(nodes, function(u) { return u; }); | |
svgNodes.selectAll('*').remove(); | |
svgNodes | |
.enter() | |
.append('g') | |
.style('opacity', 0) | |
.attr('class', 'node enter'); | |
svgNodes.each(function(u) { addLabel(graph.node(u), d3.select(this), 10, 10); }); | |
}); | |
var svgNodes = root.selectAll('g.node'); | |
return svgNodes; | |
}); | |
// Whatever margin is used for the clusters needs to be used | |
// also as an additive element to node sep | |
renderer.postRender(function (graph, root) { | |
var superGraph = new graphlib.CDigraph(); | |
var subGraphs = graph.children(null); | |
var nodes = graph.nodes(); | |
var clusters = root.selectAll('g.cluster'); | |
clusters.each(function (sg) { | |
var cluster = d3.select(this); | |
var bbox = cluster.node().getBBox(); | |
var xPos = -(bbox.width/2 + margin/2); | |
var yPos = -(bbox.height/2 + margin/2); | |
cluster | |
.insert('rect', ':first-child') | |
.attr('id', sg) | |
.attr('x', bbox.x-margin/2) | |
.attr('y', bbox.y-margin/2) | |
.attr('width', bbox.width+margin) | |
.attr('height', bbox.height+margin) | |
.attr('fill', '#e9e9e9') | |
.attr('stroke', 'black') | |
.attr('stroke-width', '1.5px') | |
.style('opacity', 0.6); | |
superGraph.addNode(sg, {width: bbox.width+margin, height: bbox.height+margin}); | |
}); | |
// Need to add cross cluster edges to supergraph | |
graph.eachEdge(function (e, u, v, label) { | |
var c0 = graph.parent(u); | |
var c1 = graph.parent(v); | |
if (c0 != c1) { | |
superGraph.addEdge(null, c0, c1, {}); | |
} | |
}); | |
// Layout just supergraph nodes now that we have their dimensions | |
var superGraphLayout = dagreD3.layout().run(superGraph); | |
// Transform clusters with new layout | |
clusters.attr('transform', function (sg) { | |
var cluster = superGraphLayout.node(sg); | |
var newX = cluster.x+cluster.width/2; | |
var newY = cluster.y+cluster.height/2; | |
return 'translate('+newX+','+newY+')'; | |
}); | |
d3.selection.prototype.moveToFront = function() { | |
return this.each(function(){ | |
this.parentNode.appendChild(this); | |
}); | |
}; | |
var svgEdgeLabels = svg.select('g.edgeLabels'); | |
var svgEdgePaths = svg.select('g.edgePaths'); | |
svgEdgeLabels.moveToFront(); | |
svgEdgePaths.moveToFront(); | |
function calcPoints(e) { | |
var value = graph.edge(e); | |
var sourceId = graph.incidentNodes(e)[0]; | |
var targetId = graph.incidentNodes(e)[1]; | |
var source = graph.node(sourceId); | |
var target = graph.node(targetId); | |
var points = value.points.slice(); | |
var p0 = points.length === 0 ? target : points[0]; | |
var p1 = points.length === 0 ? source : points[points.length - 1]; | |
points.unshift(intersectRect(source, p0)); | |
points.push(intersectRect(target, p1)); | |
var clusterSource = graph.parent(sourceId); | |
var clusterTarget = graph.parent(targetId); | |
if (clusterSource === clusterTarget) { | |
var cluster = superGraphLayout.node(clusterSource); | |
points = points.map(function (point) { | |
point.x = point.x + cluster.x + cluster.width/2; | |
point.y = point.y + cluster.y + cluster.height/2; | |
return point; | |
}); | |
} else { | |
var c0 = superGraphLayout.node(clusterSource); | |
var c1 = superGraphLayout.node(clusterTarget); | |
var midPoint = {x: (c0.x+c0.width/2 + c1.x+c1.width/2)/2, y: (c0.y+c0.height/2 + c1.y+c1.height/2)/2}; | |
var newPoints = []; | |
newPoints[0] = {x: points[0].x + c0.x + c0.width/2, y: points[0].y + c0.y + c0.height/2}; | |
for (var i = 1; i < points.length - 1; i++) { | |
newPoints[i] = {x: points[i].x + midPoint.x, y: points[i].y + midPoint.y}; | |
} | |
newPoints[points.length-1] = {x: points[points.length-1].x + c1.x + c1.width/2, y: points[points.length-1].y + c1.y + c1.height/2}; | |
points = newPoints; | |
} | |
return d3.svg.line() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return d.y; }) | |
.interpolate('bundle') | |
.tension(0.95) | |
(points); | |
} | |
function intersectRect(rect, point) { | |
var x = rect.x; | |
var y = rect.y; | |
// For now we only support rectangles | |
// Rectangle intersection algorithm from: | |
// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes | |
var dx = point.x - x; | |
var dy = point.y - y; | |
var w = rect.width / 2; | |
var h = rect.height / 2; | |
var sx, sy; | |
if (Math.abs(dy) * w > Math.abs(dx) * h) { | |
// Intersection is top or bottom of rect. | |
if (dy < 0) { | |
h = -h; | |
} | |
sx = dy === 0 ? 0 : h * dx / dy; | |
sy = h; | |
} else { | |
// Intersection is left or right of rect. | |
if (dx < 0) { | |
w = -w; | |
} | |
sx = w; | |
sy = dx === 0 ? 0 : w * dy / dx; | |
} | |
return {x: x + sx, y: y + sy}; | |
} | |
svgEdgePaths.selectAll('path') | |
.attr('d', calcPoints); | |
oldPostRender(graph, root); | |
}); | |
renderer.run(g, svg); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment