Skip to content

Instantly share code, notes, and snippets.

@chloerulesok
Last active November 4, 2017 20:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chloerulesok/42a228e1afc16794289fc822b3c3d679 to your computer and use it in GitHub Desktop.
Save chloerulesok/42a228e1afc16794289fc822b3c3d679 to your computer and use it in GitHub Desktop.
WIP: Fizzy
license: mit
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.x.axis .domain {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 50, right: 50, bottom: 50, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
// padding between nodes
padding = 2,
maxRadius = 1000,
numberOfNodes = 50;
// Create random node data.
var data = d3.range(numberOfNodes).map(function() {
var value = Math.floor(Math.random() * 50) / 10,
size = 1000,
datum = {value: value, size: size};
return datum;
});
var x = d3.scale.linear()
.domain( [0, 5] )
.range( [margin.left, width + margin.right ] );
// Map the basic node data to d3-friendly format.
var nodes = data.map(function(node, index) {
return {
idealradius: node.size / 100,
radius: 0,
// Give each node a random color.
color: '#ff7f0e',
// Set the node's gravitational centerpoint.
idealcx: x(node.value),
idealcy: height / 2,
x: x(node.value),
// Add some randomization to the placement;
// nodes stacked on the same point can produce NaN errors.
y: height / 2 + Math.random()
};
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0)
.charge(0)
.on("tick", tick)
.start();
var xAxis = d3.svg.axis()
.scale(x);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var loading = svg.append("text")
.attr("x", ( width + margin.left + margin.right ) / 2)
.attr("y", ( height + margin.top + margin.bottom ) / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");
/**
* On a tick, apply custom gravity, collision detection, and node placement.
*/
function tick(e) {
for ( i = 0; i < nodes.length; i++ ) {
var node = nodes[i];
/*
* Animate the radius via the tick.
*
* Typically this would be performed as a transition on the SVG element itself,
* but since this is a static force layout, we must perform it on the node.
*/
node.radius = node.idealradius - node.idealradius * e.alpha * 10;
node = gravity(.2 * e.alpha)(node);
node = collide(.5)(node);
node.cx = node.x;
node.cy = node.y;
}
}
/**
* On a tick, move the node towards its desired position,
* with a preference for accuracy of the node's x-axis placement
* over smoothness of the clustering, which would produce inaccurate data presentation.
*/
function gravity(alpha) {
return function(d) {
d.y += (d.idealcy - d.y) * alpha;
d.x += (d.idealcx - d.x) * alpha * 3;
return d;
};
}
/**
* On a tick, resolve collisions between nodes.
*/
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
return d;
};
}
/**
* Run the force layout to compute where each node should be placed,
* then replace the loading text with the graph.
*/
function renderGraph() {
// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
// Of course, don't run too long—you'll hang the page!
force.start();
for (var i = 100; i > 0; --i) force.tick();
force.stop();
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + ( margin.top + ( height * 3/4 ) ) + ")")
.call(xAxis);
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.style("fill", function(d) { return d.color; })
.attr("cx", function(d) { return d.x} )
.attr("cy", function(d) { return d.y} )
.attr("r", function(d) { return d.radius} );
loading.remove();
}
// Use a timeout to allow the rest of the page to load first.
setTimeout(renderGraph, 10);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment