Custering with D3-fuse demonstration.
Last active
December 18, 2019 02:16
-
-
Save Andrew-Reid/0d7b626f4b83429fff94e79e3884ab60 to your computer and use it in GitHub Desktop.
D3-Fuse Demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*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 }); | |
}))); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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