d3-cluster compiled from @stormpython and @emeeks’ https://github.com/stormpython/d3-cluster
Built with blockbuilder.org
license: mit |
d3-cluster compiled from @stormpython and @emeeks’ https://github.com/stormpython/d3-cluster
Built with blockbuilder.org
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-scale')) : | |
typeof define === 'function' && define.amd ? define(['exports', 'd3-scale'], factory) : | |
(factory((global.d3 = global.d3 || {}),global.d3)); | |
}(this, (function (exports,d3Scale) { 'use strict'; | |
function isArray(o) { | |
return Array.isArray(o); | |
} | |
function isPlainObject(o) { | |
return !isArray(o) && typeof o === 'object'; | |
} | |
function isEqualArray(target, source) { | |
return target.every(function (o, i) { | |
if (isPlainObject(o)) { | |
return isEqualObject(o, source[i]); | |
} | |
if (isArray(o)) { | |
return isEqualArray(o, source[i]); | |
} | |
return o === source[i]; | |
}); | |
} | |
function isEqualObject(target, source) { | |
return Object.keys(target).every(function (key) { | |
return target[key] === source[key]; | |
}); | |
} | |
function isEqual(target, source) { | |
if (isArray(target) && isArray(source)) { | |
return isEqualArray(target, source); | |
} | |
if (isPlainObject(target) && isPlainObject(source)) { | |
return isEqualObject(target, source); | |
} | |
return target === source; | |
} | |
function cloneArray(source) { | |
var target = []; | |
source.forEach(function (o) { | |
if (typeof o === 'object') { | |
if (isArray(o)) { | |
target.push(cloneArray(o)); | |
} else { | |
target.push(Object.assign({}, o)); | |
} | |
} else { | |
target.push(o); | |
} | |
}); | |
return target; | |
} | |
function cloneDeep(source) { | |
if (isArray(source)) { | |
return cloneArray(source); | |
} | |
if (isPlainObject(source)) { | |
return Object.assign({}, source); | |
} | |
return source; | |
} | |
// import quadtree from 'd3-quadtree'; | |
function isMissing(arr, obj) { | |
return arr.every(function (d) { | |
return !isEqual(d, obj); | |
}); | |
} | |
function cluster () { | |
var x = function (d) { return d[0]; }; | |
var y = function (d) { return d[1]; }; | |
var radius = function (d) { return d[2]; }; | |
var xScale = d3Scale.scaleLinear(); | |
var yScale = d3Scale.scaleLinear(); | |
var centroid = function (p0, p1) { return (p1 + p0) / 2; }; | |
function X(d, i) { | |
return xScale(x.call(this, d, i)); | |
} | |
function Y(d, i) { | |
return yScale(y.call(this, d, i)); | |
} | |
function clusterize(data) { | |
var clusteredPoints = []; | |
var overlappingPoints = []; // What is the point of this? | |
var modifiedData = data | |
.map(function (d, i) { | |
return { | |
x: X.call(this, d, i), | |
y: Y.call(this, d, i), | |
radius: radius.call(this, d, i), | |
point: cloneDeep(d) // copy of original data point | |
}; | |
}) | |
.sort(function (a, b) { | |
return b.radius - a.radius; | |
}); | |
// var targetPoints = cloneDeep(modifiedData); // copy of data | |
var targetPoints = modifiedData; // copy of data | |
modifiedData.forEach(function (p) { | |
if (isMissing(overlappingPoints, p)) { | |
p.overlap = []; | |
clusteredPoints.push(p); | |
targetPoints.forEach(function (t) { | |
if (t !== p) { | |
var distance = Math.sqrt(Math.pow(Math.abs(t.x - p.x), 2) + | |
Math.pow(Math.abs(t.y - p.y), 2)); | |
if (distance < (t.radius + p.radius)) { | |
t.clustered = true; | |
p.overlap.push(t); | |
overlappingPoints.push(t); | |
} | |
} | |
}); | |
targetPoints = targetPoints.filter(function (d) { | |
return isMissing(p.overlap, d); | |
}); | |
} | |
}); | |
return clusteredPoints; | |
} | |
function layout(data) { | |
return clusterize(data); | |
} | |
// Public API | |
layout.x = function (_) { | |
if (!arguments.length) { return x; } | |
x = typeof _ === 'function' ? _ : x; | |
return layout; | |
}; | |
layout.y = function (_) { | |
if (!arguments.length) { return y; } | |
y = typeof _ === 'function' ? _ : y; | |
return layout; | |
}; | |
layout.radius = function (_) { | |
if (!arguments.length) { return radius; } | |
if (typeof _ === 'function') { radius = _; } | |
if (typeof _ === 'number') { radius = function () { return _; }; } | |
return layout; | |
}; | |
layout.centroid = function (_) { | |
if (!arguments.length) { return centroid; } | |
centroid = typeof _ === 'function' ? _ : centroid; | |
return layout; | |
}; | |
layout.xScale = function (_) { | |
if (!arguments.length) { return xScale; } | |
xScale = typeof _ === 'function' ? _ : xScale; | |
return layout; | |
}; | |
layout.yScale = function (_) { | |
if (!arguments.length) { return yScale; } | |
yScale = typeof _ === 'function' ? _ : yScale; | |
return layout; | |
}; | |
return layout; | |
} | |
exports.cluster = cluster; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
}))); |
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("d3-scale")):"function"==typeof define&&define.amd?define(["exports","d3-scale"],t):t(n.d3=n.d3||{},n.d3)}(this,function(n,t){"use strict";function e(n){return Array.isArray(n)}function r(n){return!e(n)&&"object"==typeof n}function u(n,t){return n.every(function(n,i){return r(n)?o(n,t[i]):e(n)?u(n,t[i]):n===t[i]})}function o(n,t){return Object.keys(n).every(function(e){return n[e]===t[e]})}function i(n,t){return e(n)&&e(t)?u(n,t):r(n)&&r(t)?o(n,t):n===t}function c(n){var t=[];return n.forEach(function(n){"object"==typeof n?e(n)?t.push(c(n)):t.push(Object.assign({},n)):t.push(n)}),t}function f(n){return e(n)?c(n):r(n)?Object.assign({},n):n}function a(n,t){return n.every(function(n){return!i(n,t)})}function s(){function n(n,t){return s(o.call(this,n,t))}function e(n,t){return l(i.call(this,n,t))}function r(t){var r=[],u=[],o=t.map(function(t,r){return{x:n.call(this,t,r),y:e.call(this,t,r),radius:c.call(this,t,r),point:f(t)}}).sort(function(n,t){return t.radius-n.radius}),i=o;return o.forEach(function(n){a(u,n)&&(n.overlap=[],r.push(n),i.forEach(function(t){if(t!==n){Math.sqrt(Math.pow(Math.abs(t.x-n.x),2)+Math.pow(Math.abs(t.y-n.y),2))<t.radius+n.radius&&(t.clustered=!0,n.overlap.push(t),u.push(t))}}),i=i.filter(function(t){return a(n.overlap,t)}))}),r}function u(n){return r(n)}var o=function(n){return n[0]},i=function(n){return n[1]},c=function(n){return n[2]},s=t.scaleLinear(),l=t.scaleLinear(),p=function(n,t){return(t+n)/2};return u.x=function(n){return arguments.length?(o="function"==typeof n?n:o,u):o},u.y=function(n){return arguments.length?(i="function"==typeof n?n:i,u):i},u.radius=function(n){return arguments.length?("function"==typeof n&&(c=n),"number"==typeof n&&(c=function(){return n}),u):c},u.centroid=function(n){return arguments.length?(p="function"==typeof n?n:p,u):p},u.xScale=function(n){return arguments.length?(s="function"==typeof n?n:s,u):s},u.yScale=function(n){return arguments.length?(l="function"==typeof n?n:l,u):l},u}n.cluster=s,Object.defineProperty(n,"__esModule",{value:!0})}); |
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="d3-cluster.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
svg { | |
height: 1000px; | |
width: 1000px; | |
border: 1px solid lightgray; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="viz"> | |
<svg class="main"> | |
</svg> | |
</div> | |
</body> | |
<footer> | |
<script> | |
const colors = [ | |
"#00a2ce", | |
"#4d430c", | |
"#b3331d", | |
"#b6a756" | |
] | |
const testData = [] | |
for (let x=1;x<500;x++) { | |
testData.push({ x: 10 + Math.random() * 940, y: 10 + Math.random() * 480, r: 1 + Math.random() * 20, color: colors[x%4] }) | |
} | |
d3.select("svg") | |
.selectAll("circle") | |
.data(testData) | |
.enter() | |
.append("circle") | |
.attr("r", d => d.r) | |
.attr("cx", d => d.x) | |
.attr("cy", d => d.y) | |
.style("fill", d => d.color) | |
.style("fill-opacity", 0.5) | |
setTimeout(() => { | |
const cluster = d3.cluster() | |
.x(d => d.x) | |
.y(d => d.y) | |
.radius(d => d.r); | |
d3.select("svg") | |
.selectAll("circle.collision") | |
.data(cluster(testData)) | |
.enter() | |
.append("circle") | |
.attr("r", d => d.radius) | |
.attr("cx", d => d.x) | |
.attr("cy", d => d.y) | |
.style("fill", "#FCBC34") | |
.style("fill-opacity", d => d.overlap.length === 0 ? 1 : 0) | |
.style("stroke", d => d.overlap.length === 0 ? "none" : "#FE9922") | |
.style("stroke-width", d => d.overlap.length + 1) | |
}, 1000) | |
</script> | |
</footer> |