Skip to content

Instantly share code, notes, and snippets.

@basilesimon
Created November 2, 2017 17:22
Show Gist options
  • Save basilesimon/ee47d5adad006b5cb88feecf93480cc6 to your computer and use it in GitHub Desktop.
Save basilesimon/ee47d5adad006b5cb88feecf93480cc6 to your computer and use it in GitHub Desktop.
Enclosing Force Circles
license: mit

Using d3.packEnclose to draw larger circles around circles forced towards a single point.

Visualising London university pay ratios. The number of dots in each enclosed circle indicates the ratio between the lowest and highest paid at each university. Hovering over each circle reveals the ratio.

forked from tlfrd's block: Enclosing Force Circles

forked from anonymous's block: Enclosing Force Circles

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="paygaps.js"></script>
<style>
body { margin: 0; position: fixed; top: 0; right: 0; bottom: 0; left: 0; }
.enclosing-circle {
fill-opacity: 0.05;
}
</style>
</head>
<body>
<script>
var margin = { top: 50, right: 50, bottom: 50, left: 50 };
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var radius = 5;
var initPos = { x: 120, y: 100 },
paddingX = 250,
paddingY = 150;
var rowLength = 8;
function sortBy(attribute, order) {
councils.sort(function(a, b) {
if(a[attribute] < b[attribute]) return -1 * order;
if(a[attribute] > b[attribute]) return 1 * order;
return 0;
});
}
councils.forEach(function(p, i) {
var dots = d3.range(p.max).map(function(d) {
return {
circleId: i,
id: d
}
});
var simulation = d3.forceSimulation(dots)
.force("x", d3.forceX(initPos.x + paddingX * (i % rowLength)).strength(2))
.force("y", d3.forceY(initPos.y + paddingY * (Math.floor(i / rowLength))).strength(1))
.force("collide", d3.forceCollide(radius + 1).iterations(10))
.stop();
for (var x = 0; x < 120; ++x) simulation.tick();
addCircles(dots, i);
})
function addCircles(data, i) {
var circles = svg.append("g")
.attr("class", "circles")
.selectAll("g").data(data)
.enter().append("g")
.each(function(d) {
d.r = radius;
})
.style("fill", 'steelblue')
.style('opacity', .8);
circles.append("circle")
.attr("r", radius)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
var enclosingCircleAttr = d3.packEnclose(circles.data());
var enclosingCircle = svg.append("g")
.append("circle")
.datum(enclosingCircleAttr)
.attr("class", "enclosing-circle")
.attr("r", function(d) {
return d.r + radius;
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
}
</script>
</body>
var councils = [
{name: "all", max: 434},
{name: "replied", max: 248},
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment