Automatic generation of flat network structure with the Voronoi algorithm.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="//d3js.org/d3.v3.min.js"></script>
</head>
<style>
.node {
stroke: #fff;
stroke-width: 2;
cursor: pointer;
}
.link {
stroke-width: 2;
}
</style>
<body>
<svg class="gravity">
<g class="links-container"></g>
<g class="nodes-container"></g>
</svg>
<script type="text/javascript" src="main.js"></script>
</body>
</html>
// Chart Parameters
var numNodes = 30,
width = 600,
height = 600,
r = 15,
b = 0.01;
var c0 = '#FF6B6B',
c1 = '#490A3D';
// Generate the nodes (data)
var nodes = d3.range(numNodes).map(function(k) { return {num: k}; }),
links = [];
var circles,
lines;
// Compute the initial positions with the force layout
var f0 = d3.layout.force()
.size([width, height])
.nodes(nodes)
.charge(-250)
.start();
// Cool down the force
while (f0.alpha() > 1e-5) {
f0.tick();
}
var svg = d3.select('.gravity'),
gnodes = svg.select('g.nodes-container'),
glinks = svg.select('g.links-container');
svg.attr('width', width).attr('height', height);
var circles = gnodes.selectAll('circle.node').data(nodes);
circles.enter().append('circle').classed('node', true);
circles
.attr('r', r)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
circles.exit().remove();
updateCircles();
// Compute the links with the voronoi triangulation
links = d3.geom.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.links(nodes);
var lines = glinks.selectAll('line.link').data(links);
lines.enter().append('line').classed('link', true);
lines.exit().remove();
var h = d3.scale.linear()
.domain([0, Math.sqrt(width * width + height * height) / 2])
.range([2, 6]);
var c = d3.scale.linear()
.domain(h.domain())
.range([c0, c1]);
updateLines();
function updateCircles() {
circles
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('fill', function(d) { return d.l ? c(d.l) : c0; });
}
function updateLines() {
lines
.attr('x1', function(d) { return d.source.x; })
.attr('y1', function(d) { return d.source.y; })
.attr('x2', function(d) { return d.target.x; })
.attr('y2', function(d) { return d.target.y; })
.attr('stroke', function(d) {
if (d.source.l || d.target.l) {
return d.source.l > d.target.l ? c(d.source.l) : c(d.target.l);
}
return c0;
});
}
var u = nodes[10];
nodes.forEach(function(d) {
var dx = d.x - u.x,
dy = d.y - u.y;
var l = Math.sqrt(dx * dx + dy * dy);
d.l = l;
});
updateCircles();
updateLines();
circles.on('click', function(u) {
nodes.forEach(function(d) {
var dx = d.x - u.x,
ux = dx / Math.abs(dx),
dy = d.y - u.y,
uy = dy / Math.abs(dy);
var l = Math.sqrt(dx * dx + dy * dy);
d.x = d.index !== u.index ? d.x + b * ux * h(l) : d.x;
d.y = d.index !== u.index ? d.y + b * uy * h(l) : d.y;
d.l = l;
});
f0.resume();
});
f0.on('tick', function() {
updateCircles();
updateLines();
});