Clustering points into pie charts with d3-fuse. The size of each point represents how many places of worship from Open Street Map are present. Background map with an experimental tile module.
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="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 = [-0.2164171,51.5050712];
var colors = ["steelblue","#b2df8a","#fb9a99","#ccc","#33a02c","#e31a1c"]
var religions = ["christian","jewish","muslim","sikh","hindu","buddhist"];
var slippy = d3.geoSlippy()
.size(svg)
.center(center)
.scale(700000/Math.PI/2)
// Create a g for the tiles:
var raster = svg.append("g");
d3.json("LondonReligions.geojson").then(function(geojson) {
console.log(geojson);
// Set up nodes:
var data = geojson.features.map(function(feature) {
return { "lat" : feature.geometry.coordinates[1] , "long" : feature.geometry.coordinates[0], religion: feature.properties.religion }
})
var religions = [];
data.forEach(function(d) {
if(religions.indexOf(d) == -1) religions.push(d);
})
data.forEach(place);
// Set up cluster
var cluster = d3.fuse()
.nodes(data)
.fuse();
raster.call(slippy.tile);
data.forEach(tally);
draw(data);
})
// Helper functions:
function place(node) {
var p = slippy([+node.long,+node.lat]);
node.x = p[0];
node.y = p[1];
node.r = 8;
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) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
var slices = d.totals;
var radius = d.layout.r;
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var arc = d3.arc()
.outerRadius(radius)
.innerRadius(0)
.context(ctx);
ctx.translate(d.layout.x, d.layout.y);
var arcs = pie(slices);
arcs.forEach(function(d, i) {
ctx.beginPath();
arc(d);
ctx.fillStyle = colors[i];
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);
}
// Tally children by religion:
function tally(node) {
var counts = [0,0,0,0,0,0];
religions.forEach(function(r,i) {
if(r == node.religion) counts[i]++;
})
if(node.layout.children.length > 0) {
node.layout.children.forEach(function(child) {
religions.forEach(function(r,i) {
if(r == child.religion) counts[i]++;
})
})
}
node.totals = counts;
}
// Add legend:
var legend = svg.append("g")
.selectAll("rect")
.data(religions)
.enter()
.append("g")
.attr("transform",function(d,i) { return "translate("+[5,i*30+30]+")"; });
legend.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("fill",function(d,i) {
console.log(colors[i]);
return colors[i];
});
legend.append("text")
.attr("dx", 25)
.attr("dy", 15)
.attr("font-size","14")
.text(function(d) { return d.charAt(0).toUpperCase() + d.substring(1); })
</script>
https://d3js.org/d3.v5.min.js