Skip to content

Instantly share code, notes, and snippets.

@Andrew-Reid
Last active December 18, 2019 02:16
Show Gist options
  • Save Andrew-Reid/0d7b626f4b83429fff94e79e3884ab60 to your computer and use it in GitHub Desktop.
Save Andrew-Reid/0d7b626f4b83429fff94e79e3884ab60 to your computer and use it in GitHub Desktop.
D3-Fuse Demo

Custering with D3-fuse demonstration.

/*v0.0.2*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-quadtree')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-quadtree'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3)); }(this, (function (exports,d3Quadtree) { 'use strict';
var c = function(f) { return (typeof f == "function") ? f : (function() { return f; }) }
var fuse = function(n) {
var nodes = n || [], padding = 0, pi = Math.PI;
var x = function(d) { return d.x; },
y = function(d) { return d.y; },
r = function(d) { return d.r; },
a = function(d) { return r(d) * r(d) * Math.PI; }
function fuse() { initializeNodes(), step(); return nodes; }
function cluster() {
var tree = d3Quadtree.quadtree(nodes, function(d) { return d.layout.x; }, function(d) { return d.layout.y; }).visitAfter(prepare);
var n0; // Current Node, n1 = comparison node.
var count = 0; // Number of merges for a given cycle
for (var i = 0; i < nodes.length; ++i) n0 = nodes[i], tree.visit(apply);
function apply(qn, x0, y0, x1, y1) {
var n1 = qn.data;
var r = qn.r + n0.layout.r;
if (n1 && n1.index > n0.index && n1.layout.a && n0.layout.a) {
var x = n0.layout.x - n1.layout.x || 1e-6;
var y = n0.layout.y - n1.layout.y || 1e-6;
var l = Math.sqrt(x * x + y * y);
if (l < r + padding) { // If merge required
l = (r - l) / l;
count++;
// Merge logic
var a,b;
if(n1.layout.a > n0.layout.a) a = n1, b = n0; // Node1 absorbs Node0
else a = n0, b = n1; // Node0 absorbs Node1
// Merge nodes:
a.layout.x = (a.layout.x * a.layout.a + b.layout.x * b.layout.a)/(a.layout.a + b.layout.a);
a.layout.y = (a.layout.y * a.layout.a + b.layout.y * b.layout.a)/(b.layout.a + a.layout.a);
a.layout.count += b.layout.count;
a.layout.a += b.layout.a;
a.layout.r = Math.sqrt(a.layout.a/pi);
b.layout.r = b.layout.a = 0;
a.layout.children.push(b), b.layout.parent = a;
}
return;
}
return x0 > n0.layout.x + r || x1 < n0.layout.x - r || y0 > n0.layout.y + r || y1 < n0.layout.y - r;
}
return count;
}
function prepare(n) {
if (n.data) return n.r = n.data.layout.r;
for (var i = n.r = 0; i < 4; ++i) {
if (n[i] && n[i].r > n.r) {
n.r = n[i].r;
}
}
}
function step() { if(cluster()) step(); }
function initializeNodes() {
for (var i = 0, n = nodes.length, node; i < n; ++i) {
node = nodes[i], node.index = i;
node.layout = { x:x(node), y:y(node), a: a(node), r: r(node), count: 1, children: [], parent: {} }
}
}
fuse.nodes = function(_) { return arguments.length ? (nodes = _, fuse) : nodes; }
fuse.padding = function(_) { return arguments.length ? (padding = _, fuse) : padding; }
fuse.radius = function(_) { return arguments.length ? (r = c(_), fuse) : r; }
fuse.area = function(_) { return arguments.length ? (a = c(_), fuse) : a; }
fuse.x = function(_) { x = c(_); return fuse; }
fuse.y = function(_) { y = c(_); return fuse; }
fuse.defuse = function() { nodes.forEach(function(n) { delete n.layout; }); return fuse; }
fuse.step = function() { initializeNodes(); cluster(); return fuse; }
fuse.fuse = function() { fuse(); return fuse; }
return fuse;
};
exports.fuse = fuse;
Object.defineProperty(exports, '__esModule', { value: true });
})));
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.js"></script>
<script src="d3-fuse.js"></script>
<body>
<script>
var width = 960;
var height = 500
var svgCluster = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// Create nodes:
var nodes = d3.range(1200).map(function(d) {
return {
index: d,
r: 3,
a: Math.PI,
x: Math.random()*width,
y: Math.random()*height
}
})
// Set up cluster
var cluster = d3.fuse()
.nodes(nodes);
cluster();
var circles = svgCluster.selectAll("circle")
.data(nodes)//.filter(function(d) { return d.r != 0; }))
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; })
.attr("fill","#a8ddb5");
function parent(d) {
while(d.layout && d.layout.parent && d.layout.parent.x) {
d = d.layout.parent;
}
return d;
}
var scale = d3.scaleLinear()
//.range(["#a8ddb5","#43a2ca","#0868ac"])
.range(["#ccebc5","#7bccc4","#0868ac"])
.domain([3,12,40]);
var k = 1;
var grow = true;
d3.interval(function() {
grow ? k*=1.3 : k/= 1.3;
if(k > 2.5) grow = false;
if(k < 1) grow = true;
nodes.forEach(function(node) {
node.r = 3 * k;
node.a = Math.PI * node.r * node.r;
})
cluster();
circles.sort(function(a,b) {
parent(a).layout.r - parent(b).layout.r;
})
circles.transition()
.attr("cx", function(d) { return parent(d).layout.x; })
.attr("cy", function(d) { return parent(d).layout.y; })
.attr("r", function(d) { return parent(d).layout.r; })
.attr("fill", function(d) { return scale(parent(d).layout.r); })
.duration(1000);
}, 1500);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment