This is a second demonstration of d3-fuse, a clustering library that allows the visual fusion of nodes that are overlapping.
See also this example.
Zooming will recalculate clusters while panning will simply move existing clusters.
The tile map is based on an as of yet experimental slippy map library for D3.
xxxxxxxxxx
<meta charset="utf-8">
<style>
svg, canvas {
position: absolute;
top: 0px;
left: 0px;
}
</style>
<svg width="960" height="500"></svg>
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-tile.v0.0.min.js"></script>
<script src="d3-fuse.js"></script>
<script src="d3-slippy.js"></script>
<script>
// Canvas and SVG:
var canvas = d3.select("canvas");
var ctx = canvas.node().getContext("2d");
var svg = d3.select("svg");
// Basic parameters:
var width = +canvas.attr("width");
var height = +canvas.attr("height");
var center = [-75.5, 38];
var slippy = d3.geoTile()
.tileSet("ESRI_OceanBasemap")
.size(svg)
.center(center)
.scale(32768/Math.PI/2)
.zoomTranslate([0,0])
// Create a g for the tiles:
var raster = svg.append("g");
d3.csv("shipwrecks.csv").then(function(data) {
// Set up nodes:
data.forEach(place)
// Set up cluster
var cluster = d3.fuse()
.nodes(data)
.fuse(); // calculate clusters for initial load.
// Set up zoom.
var zoom = d3.zoom()
.on("zoom",zoomed);
// Call the zoom:
canvas.call(zoom)
.call(zoom.transform, slippy.zoomIdentity());
function zoomed() {
var k = d3.event.transform.k;
// Redraw clusters if scale changes
if(slippy.zoomScale() != k) {
slippy.zoomTransform(d3.event.transform)
data.forEach(place);
draw(cluster());
}
// Only translate the clusters if scale doesn't change:
else {
var t0 = slippy.zoomTranslate();
var t1 = [d3.event.transform.x,d3.event.transform.y];
data.forEach(function(d) {
d.layout.x += t1[0] - t0[0];
d.layout.y += t1[1] - t0[1];
})
draw(data);
slippy.zoomTranslate(t1)
}
raster.call(slippy.tile);
}
})
// Helper functions:
function place(node) {
var p = slippy([+node.long,+node.lat]);
node.x = p[0];
node.y = p[1];
node.r = 2;
node.a = Math.PI * node.r * node.r;
node.count = 1;
}
// Drawing functions:
function draw(nodes) {
ctx.clearRect(0,0,width,height);
nodes.filter(function(d) { return d.layout.r; }).forEach(drawCircle);
nodes.filter(function(d) { return d.layout.r > 20; }).forEach(drawText);
attribute();
}
function drawCircle(d) {
d = d.layout;
ctx.fillStyle = d.r ? ( d.r > 20 ? "#a8ddb5" : "#43a2ca" ) : "#0868ac";
ctx.beginPath();
ctx.moveTo(d.x, d.y);
ctx.arc(d.x, d.y, d.r, 0, 2 * Math.PI);
ctx.fill();
}
function drawText(d) {
d = d.layout;
ctx.font = d.r / 3 + "px Arial";
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText(d.count,d.x,d.y+d.r/9);
}
function attribute() {
ctx.font = "10px Arial";
ctx.textAlign = "left";
ctx.fillStyle = "black";
ctx.fillText("Tiles \u00A9 Esri - Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri", 4, height-4);
}
</script>
https://d3js.org/d3.v5.min.js
https://d3js.org/d3-tile.v0.0.min.js