Skip to content

Instantly share code, notes, and snippets.

@alienrobotwizard
Created March 28, 2014 19:54
Show Gist options
  • Save alienrobotwizard/9841692 to your computer and use it in GitHub Desktop.
Save alienrobotwizard/9841692 to your computer and use it in GitHub Desktop.
<!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