Skip to content

Instantly share code, notes, and snippets.

@Andrew-Reid
Last active December 18, 2019 06:10
Show Gist options
  • Save Andrew-Reid/cd6c23214147ddea4336b1f50238e3d6 to your computer and use it in GitHub Desktop.
Save Andrew-Reid/cd6c23214147ddea4336b1f50238e3d6 to your computer and use it in GitHub Desktop.
D3-Fuse Clustering with pie charts

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.

/*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 });
})));
// Andrew Reid 2018
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3));
}(this, function (exports) { 'use strict';
function geoTile() {
// Basic Constants
const tau = Math.PI * 2;
var lim = 85.05113;
var tileSize = 256;
// Map Properties:
var w = 960;
var h = 500;
// Projection Values:
var pk = w/tau; // projection scale k
var pc = [0,0] // projection geographic center
var pr = 0 // central longitude for rotation.
// Zoom Transform Values:
var tk = 1; // zoom transform scale k
var tx = w/2; // zoom transform translate x
var ty = h/2; // zoom transform translate y
// Offsets for projections where the north west limit is no in the top left.
var ox = function() { return 0 };
var oy = function() { return 0 };
// The Projection:
var p = d3.geoMercator()
.scale(pk)
.center(pc);
// Tile wrapping and zoom limits:
var z0 = 4;
var z1 = 13;
var extent = function() { return {left:-179.99999,top:lim,right:179.9999,bottom:-lim}; };
var wrap = true;
// Tile ordering
var xyz = true;
// Tile source & attribution
var source = function(d) {
return "http://" + "abc"[d.y % 3] + ".tile.openstreetmap.org/" + d.z + "/" + d.x + "/" + d.y + ".png";
}
var a = "Tiles © OpenStreetMap contributors";
// Tile ordering:
geoTile.xyz = function(_) {
return arguments.length ? (xyz = _, geoTile): xyz;
}
function geoTile(_) {
return p(_);
}
// General Methods
geoTile.width = function(_) {
return arguments.length ? (w = _, geoTile) : w;
}
geoTile.height = function(_) {
return arguments.length ? (h = _, geoTile) : h;
}
geoTile.size = function(_) {
if(arguments.length) {
(_ instanceof d3.selection) ? (w = _.attr("width"), h = _.attr("height"), tx = w/2, ty = h/2) : (w = _[0], h = _[1]);
return geoTile;
}
else return [w,h]
}
geoTile.source = function(_) {
return arguments.length ? (source = _, geoTile) : source;
}
geoTile.projection = function() {
return p;
}
geoTile.attribution = function(_) {
return arguments.length ? (a = _, geoTile) : a;
}
geoTile.wrap = function(_) {
return arguments.length ? (wrap = _, geoTile) : wrap;
}
// Projection methods:
geoTile.invert = function(_) {
return p.invert(_);
}
geoTile.center = function(_) {
// Need to account for any rotation (divergence from d3.geoProjection typical behavior //
var rotate = d3.geoRotation(p.rotate())
if(arguments.length) {
pc = rotate(_); p.center(pc); return geoTile;
}
else {
return rotate.invert(pc);
}
//return arguments.length ? (/*_[0] -= p.rotate()[0], _[1] -= p.rotate()[1], */ pc = _, p.center(pc), geoTile): pc;
}
geoTile.scale = function(_) {
return arguments.length ? (pk = _, p.scale(pk), geoTile) : pk;
}
geoTile.rotate = function(_) {
return arguments.length ? (pr = _, p.rotate([pr,0]), geoTile) : pr;
}
geoTile.fit = function(_) {
return arguments.length ? (p.fitSize([w,h],_),tx = p.translate()[0],ty = p.translate()[1],pk = p.scale(), geoTile) : "n/a";
}
geoTile.fitMargin = function(m,f) {
return arguments.length > 1 ? (p.fitExtent([[m,m],[w-m,h-m]],f), tx = p.translate()[0],ty = p.translate()[1],pk = p.scale(), geoTile) : "n/a";
}
geoTile.offset = function(_) {
return arguments.length ? (ox = _[0], oy = _[1], geoTile): [ox,oy];
}
// Zoom Methods:
geoTile.zoomScale = function(_) {
return arguments.length ? (tk = _, p.scale(pk*tk), geoTile) : tk;
}
geoTile.zoomTranslate = function(_) {
return arguments.length ? (tx = _[0], ty = _[1], p.translate([tx, ty]), geoTile): [tx,ty]
}
geoTile.zoomIdentity = function() {
return d3.zoomIdentity.translate(tx,ty).scale(tk).translate(0,0);
}
geoTile.zoomTransform = function(t) {
tx = t.x, ty = t.y, tk = t.k; p.translate([tx,ty]); p.scale(pk*tk); return geoTile;
}
// Get scale factor for tile depth:
geoTile.tileDepth = function(z) {
if(arguments.length) {
return Math.pow(Math.E, ((z + 8) * Math.LN2)) / pk / tau;
}
else {
var size = pk * tk * tau;
var z = Math.max(Math.log(size) / Math.LN2 - 8, 0);
return Math.round(z);
}
}
// Set tile depth:
geoTile.setTileDepth = function(_) {
pk *= geoTile.tileDepth(_); p.scale(pk); return geoTile;
}
// Set/get tile size:
geoTile.tileSize = function(_) {
return arguments.length ? (tileSize = _, geoTile) : tileSize;
}
// Zoom extent methods:
geoTile.zoomScaleExtent = function(_) {
if (arguments.length) {
z0 = _[0];
z1 = _[1];
return geoTile;
}
else {
var size = pk * tk * tau;
var z = Math.max(Math.log(size) / Math.LN2 - 8, 0);
var max = Math.pow(2,z1)/Math.pow(2,z);
var min = Math.pow(2,z0)/Math.pow(2,z);
return [min,max];
}
}
geoTile.zoomTranslateExtent = function(_) {
var e = extent();
if (arguments.length) {
e.left = _[0][0];
e.top = _[0][1];
e.right = _[1][0];
e.bottom = _[1][1];
return geoTile;
}
else {
var x0 = p([e.left-pr,e.top])[0] - tx;
var y0 = p([e.left-pr,e.top])[1] - ty;
var x1 = p([e.right-pr,e.bottom])[0] - tx;
var y1 = p([e.right-pr,e.bottom])[1] - ty;
return [[x0,y0],[x1,y1]];
}
}
geoTile.zoomTranslateConstrain = function() {
var e = extent();
e.left = p.invert([0,0])[0];
e.top = p.invert([0,0])[1];
e.right = p.invert([w,h])[0];
e.bottom = p.invert([w,h])[1];
var x0 = p([e.left-pr,e.top])[0] - tx;
var y0 = p([e.left-pr,e.top])[1] - ty;
var x1 = p([e.right-pr,e.bottom])[0] - tx;
var y1 = p([e.right-pr,e.bottom])[1] - ty;
return [[x0,y0],[x1,y1]];
}
// Tile Methods:
// Calculate Tiles:
geoTile.tiles = function() {
var size = pk * tk * tau;
var z = Math.max(Math.log(size) / Math.LN2 - Math.log(tileSize)/Math.log(2), 0); // tile depth
var s = Math.pow(2, z - Math.round(z) + 8);
var y0 = p([-180,lim])[1] - oy.call(this,w,h) * tk * pk/w*tau;
var x0 = p([-180,lim])[0] - ox.call(this,w,h) * tk * pk/w*tau;
var set = [];
var cStart = wrap ? Math.floor((0 - x0) / s) : Math.max(0, Math.floor((0 - x0) / s));
var cEnd = Math.max(0, Math.ceil((w - x0) / s));
var rStart = Math.max(0,Math.floor((0 - y0) / s));
var rEnd = Math.max(0, Math.ceil((h - y0) / s));
for(var i = cStart; i < cEnd; i++) {
for(var j = rStart; j < rEnd; j++) {
var x = i;
if (wrap) {
var k = Math.pow(2,Math.round(z));
x = (i+k)%k;
}
set.push({x:x,y:j,z:Math.round(z),tx:i,ty:j, id:i+"-"+j+"-"+z})
}
}
if(!xyz) {
set.forEach(function(d) {
d.y = (Math.pow(2, d.z) - d.y - 1)
})
}
set.translate = [x0 / s, y0 / s];
set.scale = s;
return set;
}
// Assign Tiles to a Selection:
geoTile.tile = function(g) {
var set = geoTile.tiles();
var images = g.attr("transform", stringify(set.scale, set.translate))
.selectAll("image")
.data(set, function(d) { return d.id; })
images.exit().remove();
images.enter().append("image").merge(images)
.attr("xlink:href", source )
.attr("x", function(d) { return d.tx * tileSize; })
.attr("y", function(d) { return d.ty * tileSize; })
.attr("width", tileSize)
.attr("height", tileSize);
}
// Draw on a canvas:
geoTile.canvas = function(context) {
var set = geoTile.tiles();
var k = set.scale / tileSize, r = set.scale % 1 ? Number : Math.round;
var ox = r(set.translate[0] * set.scale);
var oy = r(set.translate[1] * set.scale);
set.forEach(function(d) {
var tile = new Image();
tile.src = source(d); // can also be a remote URL e.g. http://
tile.onload = function() {
context.drawImage(tile,d.tx*tileSize*k+ox,d.ty*tileSize*k+oy,tileSize*k,tileSize*k);
};
})
}
// Helper stringify
function stringify(scale, translate) {
var k = scale / tileSize, r = scale % 1 ? Number : Math.round;
return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
geoTile.tileSet = function(_) {
if(arguments.length) {
a = _.attribution ? _.attribution : "Unknown";
p = _.projection ? _.projection.scale(960/tau).translate([0,0]) : d3.geoMercator().scale(960/tau).translate([0,0]);
source = _.source ? _.source : (console.log("no source provided, using osm"), a = "Tiles © OpenStreetMap contributors", function(d) { return "http://" + "abc"[d.y % 3] + ".tile.openstreetmap.org/" + d.z + "/" + d.x + "/" + d.y + ".png"; })
lim = _.limit ? _.limit : 85.05113;
tileSize = _.tileSize ? _.tileSize : 256;
ox = _.offsetX ? _.offsetX : function() { return 0 };
oy = _.offsetY ? _.offsetY : function() { return 0 };
z0 = _.minDepth ? _.minDepth : 1;
z1 = _.maxDepth ? _.maxDepth : 13;
wrap = _.wrap ? _.wrap : false;
xyz = _.xyz ? _.xyz : true; // tile ordering
}
return geoTile;
}
return geoTile;
}
var tileSets = {
CartoDB_Positron : {
type:"tileset",
attribution: "© OpenStreetMap © CartoDB",
source: function(d) { return "https://cartodb-basemaps-b.global.ssl.fastly.net/light_all/"+d.z+"/"+d.x+"/"+d.y+".png"; }
},
CartoDB_PositronNoLabels : {
type:"tileset",
attribution: "© OpenStreetMap © CartoDB",
source: function(d) { return "https://cartodb-basemaps-b.global.ssl.fastly.net/light_nolabels/"+d.z+"/"+d.x+"/"+d.y+".png"; }
},
CartoDB_PositronOnlyLabels : {
type: "tileset",
attribution: "© OpenStreetMap © CartoDB",
source: function(d) { return "https://cartodb-basemaps-b.global.ssl.fastly.net/light_only_labels/"+d.z+"/"+d.x+"/"+d.y+".png"; }
},
CartoDB_DarkMatter : {
type: "tileset",
attribution: "© OpenStreetMap © CartoDB",
source: function(d) { return "https://cartodb-basemaps-b.global.ssl.fastly.net/dark_all/"+d.z+"/"+d.x+"/"+d.y+".png"; }
},
CartoDB_DarkMatterNoLabels : {
type: "tileset",
attribution: "© OpenStreetMap © CartoDB",
source: function(d) {return "https://cartodb-basemaps-b.global.ssl.fastly.net/dark_nolabels/"+d.z+"/"+d.x+"/"+d.y+".png"; }
},
CartoDB_DarkMatterOnlyLabels : {
type: "tileset",
attribution: "© OpenStreetMap © CartoDB",
source: function(d) { return "https://cartodb-basemaps-b.global.ssl.fastly.net/dark_only_labels/"+d.z+"/"+d.x+"/"+d.y+".png"; }
},
CartoDB_Voyager : {
type: "tileset",
attribution: "© OpenStreetMap © CartoDB",
source: function(d) { return "https://cartodb-basemaps-b.global.ssl.fastly.net/rastertiles/voyager/"+d.z+"/"+d.x+"/"+d.y+".png";}
},
ESRI_WorldTerrain : {
type: "tilset",
attribution: "Tiles © Esri - Source: USGS, Esri, TANA, DeLorme, and NPS",
source: function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png";}
},
ESRI_WorldShadedRelief : {
type: "tileset",
attribution: "Tiles © Esri - Source: Esri",
source: function(d) {return "https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
ESRI_WorldPhysical : {
type:"tileset",
attribution: "Tiles © Esri - Source: US National Park Service",
source: function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
ESRI_WorldStreetMap : {
type:"tileset",
attribution:"Tiles © Esri - Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom",
source: function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
ESRI_WorldTopoMap : {
type: "tileset",
attribution: "Tiles © Esri - Source: Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community",
source: function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
ESRI_WorldImagery : {
type:"tileset",
attribution: "Tiles © Esri - Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community",
source : function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
ESRI_OceanBasemap : {
type: "tileset",
attribution:"Tiles © Esri - Source: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri",
source: function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
ESRI_NGWorld : {
type: "tileset",
attribution: "Tiles © Esri - Source: National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC",
source : function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
ESRI_Gray : {
type: "tileset",
attribution: "Tiles © Esri - Source: Esri, DeLorme, NAVTEQ",
source : function(d) { return "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/"+d.z+"/"+d.y+"/"+d.x+".png"; }
},
OSM_Topo : {
type: "tileset",
attribution: "Tiles © OpenStreetMap contributors",
source: function(d) { return "https://tile.opentopomap.org/"+d.z+"/"+d.x+"/"+d.y+".png"; }
},
OSM: {
type:"tileset",
attribution: "Tiles © OpenStreetMap contributors",
source: function(d) { return "https://" + "abc"[d.y % 3] + ".tile.openstreetmap.org/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_Toner : {
type: "tileset",
attribution: "Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.",
source: function(d) { return "https://stamen-tiles.a.ssl.fastly.net/toner/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_TonerBackground : {
type:"tileset",
attribution: "Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.",
source: function(d) { return "https://stamen-tiles.a.ssl.fastly.net/toner-background/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_TonerLines : {
type:"tileset",
attribution:"Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.",
source: function(d) { return "https://stamen-tiles.a.ssl.fastly.net/toner-lines/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_TonerLite : {
type:"tileset",
attribution: "Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.",
source: function(d) { return "https://stamen-tiles.a.ssl.fastly.net/toner-lite/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_Terrain : {
type:"tileset",
attribution: "Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.",
source: function(d) { return "https://stamen-tiles.a.ssl.fastly.net/terrain/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_TerrainBackground : {
type:"tileset",
attribution: "Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.",
source: function(d) { return "https://stamen-tiles.a.ssl.fastly.net/terrain-background/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_TerrainLines : {
type:"tileset",
attribution: "Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.",
source: function(d) { return "https://stamen-tiles.a.ssl.fastly.net/terrain-lines/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
},
Stamen_Watercolor: {
type:"tileset",
attribution:"Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.",
source: function(d) {return "https://stamen-tiles.a.ssl.fastly.net/watercolor/" + d.z + "/" + d.x + "/" + d.y + ".png"; }
}
}
// Tilesets:
exports.tileSet = tileSets;
exports.geoSlippy = geoTile;
Object.defineProperty(exports, '__esModule', { value: true });
}));
<!DOCTYPE html>
<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>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment