Skip to content

Instantly share code, notes, and snippets.

@emo-eth
Last active September 14, 2016 03:32
Show Gist options
  • Save emo-eth/9e30181cd074a6d605f2aabd1cfd1087 to your computer and use it in GitHub Desktop.
Save emo-eth/9e30181cd074a6d605f2aabd1cfd1087 to your computer and use it in GitHub Desktop.
Layered force-directed bubbles
license=Apache License, 2.0

Roughly layer nodes in a force-bubble layout by their grouping or category, resulting in a striped-like appearance.

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
/*
* Vars, scales, and things
*/
var width = 960,
height = 500,
maxRadius = 12,
numNodes = 200,
numGroups = 10,
rowSpacing = height / numGroups, // space between each row for initial
// placement
mid = numGroups / 2, // which group to place at the middle of the
// simulation
rowMin = width / mid; // minimum width for outer edges (there are mid # of
// stripes above and below y=height/2)
var color = d3.scaleOrdinal(d3.schemeCategory10);
/*
* Setup
*/
var svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width);
// make the nodes, and position them in layers roughly making a diamond shape
var nodes = d3.range(numNodes).map(function() {
var group = Math.floor(Math.random() * numGroups);
var posneg = Math.random() < 0.5 ? -1 : 1; // left or right of x-middle
var iOffset = Math.abs(group - mid); // how many rows out from the center
// this node is
var xWidth = width - (iOffset * rowMin); // width of the row this node
// belongs to (works out to be roughly diamond shaped)
var r = Math.sqrt((group + 1) / numGroups * -Math.log(Math.random())) * maxRadius,
// fancy math borrowed from mbostock
d = {
group: group,
radius: r,
x: (width / 2) + (posneg * ((Math.random() * xWidth) / 2)), // place
// somewhere in the range of xWidth around the middle
y: (height / 2) + ((group - mid) * rowSpacing) + (Math.random() * rowSpacing)
// place somewhere in range of rowSpacing in its group's row
};
return d;
});
// Set up the force simulation with x/y forces and collision detection.
// Setting the x/y forces too strong jumbles up the middle nodes, so set
// their strength weaker than the default. Setting alphaDecay lower than the
// default (<0.0288...) gives the simulation more time to smooth out into a
// circle with weaker x/y forces (otherwise the shape can be pretty lumpy)
var simulation = d3.forceSimulation()
.force("xcenter", d3.forceX(width / 2).strength(0.07))
.force("ycenter", d3.forceY(height / 2).strength(0.07))
.force('collide', d3.forceCollide().radius(function(d) {
return d.radius + 0.5;
}).strength(1).iterations(10))
.alphaDecay([0.007]);
// place the nodes as circles
var circles = svg.selectAll('circles')
.data(nodes)
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
}).attr('cy', function(d) {
return d.y;
}).attr('r', function(d) {
return d.radius;
})
.style('fill', function(d) {
return color(d.group);
});
/*
* Runtime
*/
// assign the nodes to the simulation and set the tick function it calls
simulation.nodes(nodes)
.on('tick', tick);
/*
* Functions
*/
function tick() {
/*
* For each tick, re-draw the circles at their new positions.
*/
circles.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment