Skip to content

Instantly share code, notes, and snippets.

@sghall
Created December 29, 2013 05:13
Show Gist options
  • Save sghall/8167665 to your computer and use it in GitHub Desktop.
Save sghall/8167665 to your computer and use it in GitHub Desktop.
An example of using D3 Hexbins and Leaflet Maps. See blog post listed at the top for details and working example.
// See blog post for full details http://www.delimited.io/blog/2013/12/1/hexbins-with-d3-and-leaflet-maps
//**********************************************************************************
//******** LEAFLET HEXBIN LAYER CLASS *********************************************
//**********************************************************************************
L.HexbinLayer = L.Class.extend({
includes: L.Mixin.Events,
initialize: function (rawData, options) {
this.levels = {};
this.layout = d3.hexbin().radius(10);
this.rscale = d3.scale.sqrt().range([0, 10]).clamp(true);
this.rwData = rawData;
this.config = options;
},
project: function(x) {
var point = this.map.latLngToLayerPoint([x[1], x[0]]);
return [point.x, point.y];
},
getBounds: function(d) {
var b = d3.geo.bounds(d)
return L.bounds(this.project([b[0][0], b[1][1]]), this.project([b[1][0], b[0][1]]));
},
update: function () {
var pad = 100, xy = this.getBounds(this.rwData), zoom = this.map.getZoom();
this.container
.attr("width", xy.getSize().x + (2 * pad))
.attr("height", xy.getSize().y + (2 * pad))
.style("margin-left", (xy.min.x - pad) + "px")
.style("margin-top", (xy.min.y - pad) + "px");
if (!(zoom in this.levels)) {
this.levels[zoom] = this.container.append("g").attr("class", "zoom-" + zoom);
this.genHexagons(this.levels[zoom]);
this.levels[zoom].attr("transform", "translate(" + -(xy.min.x - pad) + "," + -(xy.min.y - pad) + ")");
}
if (this.curLevel) {
this.curLevel.style("display", "none");
}
this.curLevel = this.levels[zoom];
this.curLevel.style("display", "inline");
},
genHexagons: function (container) {
var data = this.rwData.features.map(function (d) {
var coords = this.project(d.geometry.coordinates)
return [coords[0],coords[1], d.properties];
}, this);
var bins = this.layout(data);
var hexagons = container.selectAll(".hexagon").data(bins);
var counts = [];
bins.map(function (elem) { counts.push(elem.length) });
this.rscale.domain([0, (ss.mean(counts) + (ss.standard_deviation(counts) * 3))]);
var path = hexagons.enter().append("path").attr("class", "hexagon");
this.config.style.call(this, path);
that = this;
hexagons
.attr("d", function(d) { return that.layout.hexagon(that.rscale(d.length)); })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("mouseover", function (d) {
var s=0, k=0;
d.map(function(e){
if (e.length === 3) e[2].group === 1 ? ++k : ++s;
});
that.config.mouse.call(this, [s,k]);
d3.select("#tooltip")
.style("visibility", "visible")
.style("top", function () { return (d3.event.pageY - 130)+"px"})
.style("left", function () { return (d3.event.pageX - 130)+"px";})
})
.on("mouseout", function (d) { d3.select("#tooltip").style("visibility", "hidden") });
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onAdd: function (map) {
this.map = map;
var overlayPane = this.map.getPanes().overlayPane;
if (!this.container || overlayPane.empty) {
this.container = d3.select(overlayPane)
.append('svg')
.attr("id", "hex-svg")
.attr('class', 'leaflet-layer leaflet-zoom-hide');
}
map.on({ 'moveend': this.update }, this);
this.update();
}
});
L.hexbinLayer = function (data, styleFunction) {
return new L.HexbinLayer(data, styleFunction);
};
//**********************************************************************************
//******** IMPORT DATA AND REFORMAT ***********************************************
//**********************************************************************************
d3.csv('data/coffee.csv', function (error, coffee) {
function reformat (array) {
var data = [];
array.map(function (d){
data.push({
properties: {
group: +d.group,
city: d.city,
state: d.state,
store: d.storenumber
},
type: "Feature",
geometry: {
coordinates:[+d.longitude,+d.latitude],
type:"Point"
}
});
});
return data;
}
var geoData = { type: "FeatureCollection", features: reformat(coffee) };
//**********************************************************************************
//******** CREATE LEAFLET MAP *****************************************************
//**********************************************************************************
var cscale = d3.scale.linear().domain([0,1]).range(["#00FF00","#FFA500"]);
var leafletMap = L.map('mapContainer').setView([40.7, -73.8], 11);
L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
attribution: '© 2013 OpenStreetMap © 2013 CloudMade',
key: '201db536dac5474c9eed000778739c8e',//Please don't use my key. It's free!!!
styleId:82102 //82102 108996 //Go to www.cloudmade.com to get a key.
}).addTo(leafletMap);
//**********************************************************************************
//******** ADD HEXBIN LAYER TO MAP AND DEFINE HEXBIN STYLE FUNCTION ***************
//**********************************************************************************
var hexLayer = L.hexbinLayer(geoData, {
style: hexbinStyle,
mouse: makePie
}).addTo(leafletMap);
function hexbinStyle(hexagons) {
hexagons
.attr("stroke", "black")
.attr("fill", function (d) {
var values = d.map(function (elem) {
return elem[2].group;
})
var avg = d3.mean(d, function(d) { return +d[2].group; })
return cscale(avg);
});
}
//**********************************************************************************
//******** PIE CHART ROLL-OVER ****************************************************
//**********************************************************************************
function makePie (data) {
d3.select("#tooltip").selectAll(".arc").remove()
d3.select("#tooltip").selectAll(".pie").remove()
var arc = d3.svg.arc()
.outerRadius(45)
.innerRadius(10);
var pie = d3.layout.pie()
.value(function(d) { return d; });
var svg = d3.select("#tooltip").select("svg")
.append("g")
.attr("class", "pie")
.attr("transform", "translate(50,50)");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d, i) { return i === 1 ? 'orange':'green'; });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.style("text-anchor", "middle")
.text(function (d) { return d.value === 0 ? "" : d.value; });
}
});
@sonphnt
Copy link

sonphnt commented Aug 15, 2014

I have tried your way and it works correctly but Is there a function to remove the hexbin layer, I have tried this.container.parentNode.removeChild(this.container) but not succeed

@do-me
Copy link

do-me commented Feb 9, 2021

If anyone is still interested in removing hexbin layers see this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment