Last active
October 8, 2015 13:19
-
-
Save seutje/77e7e85b1e88016d0dd0 to your computer and use it in GitHub Desktop.
Example map pin clustering based on PX distance.
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
var foo = { | |
init: function(i, el) { | |
this.wrapper = el; | |
this.baseMarkerRadius = 2.5; // Base size of a cluster. | |
this.markerRadiusExpand = 1.5; // Added radius based on cluster size (e.g. 4 in a cluster, radius = 2.5 + 4 * 1.5) | |
this.clusterDistance = 10; // Distance in pixels for clustering. | |
this.offset = 268435456; // Half the circumference of the earth at zoom level 21 and in pixels. | |
this.radius = this.offset / Math.PI; // Used for clustering logic. | |
var that = this; // Scope us a reference. | |
var width = 940, | |
height = 620; | |
var projection = d3.geo.mercator() | |
.scale((width) / 2 / Math.PI) | |
.center([0, 45]) // lol arbitrary center point. | |
.translate([width / 2, height / 2]) | |
.precision(0); | |
var path = d3.geo.path() | |
.projection(projection); | |
that.projection = projection; | |
var svg = d3.select(el).append('svg') | |
.attr('width', width) | |
.attr('height', height); | |
that.svg = svg; | |
}, | |
addPins: function() { | |
var that = this, | |
projection = that.projection, | |
svg = that.svg; | |
d3.json('/some/path', function(error, data) { | |
if (error) throw error; | |
var clustered = that.cluster.bind(that)(data.events, that.clusterDistance, 2); | |
svg.selectAll('.event-pin') | |
.data(clustered) | |
.enter() | |
.append('circle') | |
.attr('class', 'event-pin') | |
.attr('r', function(d) { | |
return that.baseMarkerRadius + (d.length * that.markerRadiusExpand); | |
}) | |
.attr('transform', function(d) { | |
return 'translate(' + projection([d[0].long, d[0].lat]) + ')'; | |
}) | |
.attr('style', 'cursor: pointer;') | |
.on('click', function(d) { | |
svg.selectAll('.map-tooltip').remove(); | |
svg.append('foreignObject') | |
.attr('width', 250) | |
.attr('height', 200) | |
.attr('class', 'map-tooltip') | |
.attr('transform', function() { | |
var proj = projection([d[0].long, d[0].lat]); | |
proj[0] += 30; | |
proj[1] -= 175; | |
if (proj[1] < 0) { | |
proj[1] = 0; | |
} | |
return 'translate(' + proj + ')'; | |
}) | |
.append('xhtml:div') | |
.attr('class', 'map-tooltip-wrapper') | |
.append('xhtml:div') | |
.attr('class', 'map-tooltip-inner') | |
.html(function() { | |
var content = ''; | |
for (var i = 0, max = d.length; i < max; i++) { | |
content += d[i].event; | |
} | |
return content; | |
}); | |
svg.selectAll('.tooltip-arrow').remove(); | |
svg.append('path') | |
.attr('d', lineFunction(lineData)) | |
.attr('class', 'tooltip-arrow') | |
.attr('transform', function() { | |
var proj = projection([d[0].long, d[0].lat]); | |
proj[1] -= 20; | |
return 'translate(' + proj + ')'; | |
}); | |
}); | |
}); | |
}, | |
cluster: function(markers, distance, zoom) { | |
var clustered = [], | |
marker, | |
target, | |
cluster, | |
pixels; | |
for (var i = 0, max = markers.length; i < max; i++) { | |
marker = markers[i]; | |
cluster = []; | |
if (!marker['done']) { | |
marker['done'] = true; | |
for (var j = 0, end = markers.length; j < end; j++) { | |
if (markers[j] !== markers[i] && !markers[j]['done']) { | |
target = markers[j]; | |
pixels = this.pixelDistance(marker['lat'], marker['long'], target['lat'], target['long'], zoom); | |
if (distance > pixels) { | |
target['done'] = true; | |
cluster.push(target); | |
} | |
} | |
} | |
cluster.push(marker); | |
clustered.push(cluster); | |
} | |
} | |
return clustered; | |
}, | |
lonToX: function(lon) { | |
return Math.round(this.offset + this.radius * lon * Math.PI / 180); | |
}, | |
latToY: function(lat) { | |
return Math.round(this.offset - this.radius * Math.log((1 + Math.sin(lat * Math.PI / 180)) / (1 - Math.sin(lat * Math.PI / 180))) / 2); | |
}, | |
pixelDistance: function (lat1, lon1, lat2, lon2, zoom) { | |
var x1 = this.lonToX(lon1), | |
y1 = this.latToY(lat1), | |
x2 = this.lonToX(lon2), | |
y2 = this.latToY(lat2); | |
return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)) >> (21 - zoom); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
An obvious flaw here is that it clusters towards the first pin it finds instead of to the middle of all the pins in that cluster.