The force-directed graph rendered in canvas instead of SVG.
forked from syntagmatic‘s block: Canvas Force-Directed Graph
Graph uses sample server data with 3149 nodes.
An excellent example of what @tonyhschu characterized as “That’s really cool - what is it?”
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>force-directed graph on canvas</title>
<style>
#canvas-box {
border: 1px solid #e1e2e3;
margin: 20px auto;
width: 960px;
height: 500px;
}
</style>
<!-- total nodes: 3149 -->
</head>
<body style="margin:0">
<div id="canvas-box"></div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="fd-canvas.js"></script>
</body>
</html>
/*
* force-directed graph example using canvas
* simulates displaying server data
*/
(function () {
"use strict";
var fd = (function () {
fd = {
width : 960,
height : 500,
canvas : '',
ctx : '',
color : d3.scale.category20(),
init : function (error, clusterData) {
var graph, clusterTree;
if (error) {
console.log('oops! bad data');
return;
}
clusterTree = fd.toTree(clusterData);
graph = fd.flatten(clusterTree);
fd.canvas = d3.select("#canvas-box").append("canvas")
.attr("width", fd.width)
.attr("height", fd.height)
.node();
fd.render(graph);
},
nestHosts : function (instances) {
// given an array of instances, returns an object with a children array of hosts
// with props suitable for tree display
var hosts = { name : 'hosts', children : [] };
var nest = d3.nest()
.key(function(d) { return d.host; })
.key(function(d) { return d.port; })
.entries(instances);
nest.forEach(function (mbr) {
var host = { name : mbr.key, children : [] };
mbr.values.forEach(function (instance) {
instance.values[0].name = instance.values[0].host.replace('.somecorp.com','') + ':' +
instance.values[0].port;
host.children.push(instance.values[0]);
});
hosts.children.push(host);
});
return hosts;
},
nestPods : function (dbs) {
// given an array of databases, returns an array of pods
// with props suitable for tree display
var pods = { name : 'pods', children : [] };
var nest = d3.nest()
.key(function(d) { return d.pod; })
.entries(dbs);
nest.forEach(function (mbr) {
var pod = { name : mbr.key, children : [] };
mbr.values.forEach(function (db) {
db.name = db.pod + ':' + db.label;
pod.children.push(db);
});
pods.children.push(pod);
});
return pods;
},
flatten : function (root) {
var nodes = [];
var links = [];
var ndx = 0;
var gdx = 0;
var buildlink = function(source, target) {
return { source: source.id, target: target.id, value: source.id };
};
var randRange = function (min, max) {
return parseInt(Math.round(min + Math.random() * (max - min)));
};
var buildNode = function(node) {
var colors = groupColor(node.group)
var x = randRange(1, fd.width)
var y = randRange(1, fd.height)
return { name: node.name, group: node.group, id: ndx, fill: colors.fill,
stroke: colors.stroke, x: x, y: y};
};
var groupColor = function(node) {
var fill = fd.color(node.group);
var stroke = d3.rgb(fill).darker(0.4).toString();
return { fill: fill, stroke: stroke };
};
var recurse = function (node, parent) {
var colors;
node.id = ndx;
ndx += 1;
node.group = gdx;
if (parent) {
links.push(buildlink(node, parent));
}
if (node.children) {
nodes.push(buildNode(node));
gdx += 1;
node.children.forEach(function (child) {
recurse(child, node)
});
} else {
colors = groupColor(node);
node.leaf = true;
node.fill = colors.fill;
node.stroke = colors.stroke;
node.x = randRange(1, fd.width)
node.y = randRange(1, fd.height)
nodes.push(node);
}
}
recurse(root);
console.log('nodes: ' + nodes.length)
return { nodes: nodes, links: links };
},
toTree : function (clusterData) {
var clusters = { name: 'clusters', children : [] }
clusterData.forEach(function(clusterDatum) {
var cluster = { name : clusterDatum.name, children : [] };
var hosts = fd.nestHosts(clusterDatum.instances);
var pods = fd.nestPods(clusterDatum.databases);
cluster.children.push(hosts);
cluster.children.push(pods);
clusters.children.push(cluster);
});
return clusters;
},
render: function(graph) {
var ctx = fd.canvas.getContext("2d");
var fit = Math.sqrt(graph.nodes.length / (fd.width * fd.height));
var charge = (-1 / fit);
var gravity = (8 * fit);
var force = d3.layout.force()
.charge(charge)
.gravity(gravity)
.linkDistance(2)
.size([fd.width, fd.height]);
force.nodes(graph.nodes)
.links(graph.links)
.start();
force.on("tick", function() {
ctx.clearRect(0,0,fd.canvas.width,fd.canvas.height);
ctx.strokeStyle = "rgba(150,150,150,0.6)";
ctx.lineWidth = 1;
ctx.beginPath();
graph.links.forEach(function(d) {
ctx.moveTo(d.source.x,d.source.y);
ctx.lineTo(d.target.x,d.target.y);
});
ctx.stroke();
ctx.lineWidth = 1.5;
graph.nodes.forEach(function(d) {
ctx.fillStyle = d.fill;
ctx.strokeStyle = d.stroke;
ctx.beginPath();
ctx.arc(d.x,d.y,5,0,2*Math.PI);
ctx.fill();
ctx.stroke();
});
});
}
};
return fd;
}()); // end fd iife
// kick the whole thing off
d3.json('clusters.json', fd.init);
}());