Skip to content

Instantly share code, notes, and snippets.

@rpgove
Created November 15, 2018 03:17
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 rpgove/1900fccdcdeea65061f7c99134c6c99f to your computer and use it in GitHub Desktop.
Save rpgove/1900fccdcdeea65061f7c99134c6c99f to your computer and use it in GitHub Desktop.
Force layout quadtrees
license: gpl-3.0
height: 600
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
overflow: visible;
}
.links line {
stroke: #888;
stroke-opacity: 0.6;
}
.nodes circle {
fill: #d62333;
stroke: #fff;
stroke-width: 1.5px;
}
text {
text-anchor: middle;
text-transform: uppercase;
font-family: sans-serif;
font-weight: bold;
fill: #ccc;
font-size: 32px;
}
.cell {
fill: none;
stroke: #ccc;
shape-rendering: crispEdges;
}
</style>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var graph = {
nodes: [
{id: 0},
{id: 1},
{id: 2},
{id: 3},
{id: 4},
{id: 5},
{id: 6},
{id: 7},
{id: 8},
{id: 9},
{id: 10},
{id: 11},
{id: 12},
{id: 13},
{id: 14},
],
links: [
{source: 0, target: 9},
{source: 3, target: 4},
{source: 3, target: 13},
{source: 13, target: 12},
{source: 13, target: 10},
{source: 10, target: 6},
{source: 10, target: 7},
{source: 6, target: 5},
{source: 7, target: 2},
{source: 7, target: 1},
{source: 10, target: 12},
{source: 12, target: 11},
{source: 11, target: 14},
{source: 8, target: 11},
{source: 8, target: 14},
{source: 8, target: 9},
{source: 9, target: 14},
{source: 7, target: 14},
]
};
var width = 300;
var height = 300;
var nodeRadius = 4;
var linkWidth = 1.5;
var i = 0;
var svg = d3.select('svg')
.attr('width', width)
.attr('height', height);
var iteration = svg.append('text')
.attr('x', width/2)
.attr('y', height/2);
var forceSim = d3.forceSimulation()
.force('center', d3.forceCenter(width/2, height/2));
var link = svg.append('g')
.attr('class', 'links')
.selectAll('line')
.data(graph.links)
.enter().append('line')
.attr('stroke-width', linkWidth);
var node = svg.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(graph.nodes)
.enter().append('circle')
.attr('r', nodeRadius);
node.append('title').text(function (d) { return d.name; });
forceSim.nodes(graph.nodes)
.stop();
forceSim.tick();
forceSim
.force('charge', d3.forceManyBody())
.force('link', d3.forceLink().id(function(d) { return d.id; }));
forceSim.force('link')
.links(graph.links);
var quadtree = d3.quadtree()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
.extent([[-1, -1], [width + 1, height + 1]])
.addAll(node.data());
var cell = svg.append('g')
.attr('class', 'cells')
.selectAll('rect')
.data(nodes(quadtree))
.enter().append('rect')
.attr('class', 'cell')
.style('stroke-opacity', 1)
.attr('x', function (d) { return d.x0; })
.attr('y', function (d) { return d.y0; })
.attr('width', function (d) { return d.y1 - d.y0; })
.attr('height', function (d) { return d.x1 - d.x0; });
// Draw the current positions and quadtree.
draw(true);
// Do several ticks of the layout.
var timer = d3.interval(function(elapsed) {
// Update the layout.
forceSim.tick();
++i;
// Compute the quadtree of the current positions.
quadtree = d3.quadtree()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
.extent([[-1, -1], [width + 1, height + 1]])
.addAll(graph.nodes);
// Draw the new positions and quadtree.
draw();
if (i >= 30) timer.stop();
}, 2000);
function draw (firstTime) {
iteration
.transition()
.delay(firstTime ? 0 : 500)
.text('Iteration ' + i);
link
.transition()
.duration(firstTime ? 0 : 500)
.delay(firstTime ? 0 : 500)
.ease(d3.easeLinear)
.attr('x1', function (d) { return d.source.x; })
.attr('x2', function (d) { return d.target.x; })
.attr('y1', function (d) { return d.source.y; })
.attr('y2', function (d) { return d.target.y; });
node
.transition()
.duration(firstTime ? 0 : 500)
.delay(firstTime ? 0 : 500)
.ease(d3.easeLinear)
.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
if (firstTime) return;
cell.transition()
.duration(250)
.ease(d3.easeQuad)
.style('stroke-opacity', 1e-6);
cell.data(nodes(quadtree))
.transition()
.duration(250)
.ease(d3.easeQuad)
.style('stroke-opacity', 1e-6);
var cellEnter = cell.enter().append('rect')
.attr('class', 'cell')
.style('stroke-opacity', 1e-6);
cellEnter
.merge(cell)
.transition()
.delay(1000)
.attr('x', function (d) { return d.x0; })
.attr('y', function (d) { return d.y0; })
.attr('width', function (d) { return d.y1 - d.y0; })
.attr('height', function (d) { return d.x1 - d.x0; })
.transition()
.duration(250)
.ease(d3.easeQuad)
.style('stroke-opacity', 1);
cell.exit()
.transition()
.duration(250)
.ease(d3.easeQuad)
.style('stroke-opacity', 1e-6)
.remove();
/*
svg.selectAll('.cell')
.transition()
.duration(firstTime ? 0 : 500)
.style('stroke-opacity', 0)
.remove();
svg.selectAll('.cell')
.data(nodes(quadtree))
.enter().append('rect')
.attr('class', 'cell')
.style('stroke-opacity', 0)
.attr('x', function (d) { return d.x0; })
.attr('y', function (d) { return d.y0; })
.attr('width', function (d) { return d.y1 - d.y0; })
.attr('height', function (d) { return d.x1 - d.x0; })
.transition()
.delay(firstTime ? 0 : 500)
.duration(firstTime ? 0 : 500)
.style('stroke-opacity', 1);
*/
}
function nodes(quadtree) {
var nodes = [];
quadtree.visit(function(node, x0, y0, x1, y1) {
node.x0 = x0, node.y0 = y0;
node.x1 = x1, node.y1 = y1;
nodes.push(node);
});
return nodes;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment