D3: Density


One way to plot density is on the points directly. We simply compute the KDE only on our points, and then apply an appropriate color scale. Let’s revisit Assignment 3 to see how this helps better discern points:


We can also plot density-based maps in D3 using d3.contourDensity. This performs kernel density estimation for us, where we can specify the parameters of interest - in particular, bandwidth. This will provide us with a way to generate path elements, where each path element corresponds to a binned region of density, and we can control how many path elements we would like to be drawn.

Here is an example for car crash incidents in Nashville:

d3.json('tn.json').then(function(data_1)  {
	d3.csv('car-crashes.csv').then(function(data_2)  {
		tn_counties = data_1;
		car_crashes = data_2;

function plot_contours()  {
	var path = d3.geoPath();

	var svg0 = d3.select('#svg0');
	var width = +svg0.attr('width'), height = +svg0.attr('height');

	tn_counties.features = tn_counties.features.filter(county => county.properties.COUNTYFP=='037');

	var all_incidents = [];
	car_crashes.forEach(function(d)  {

	var projection = d3.geoAzimuthalEqualArea().rotate([90,0]).fitSize([width,height], tn_counties)
	var geo_generator = d3.geoPath().projection(projection)

	var projected_incidents = all_incidents.map(d => projection(d));

	var incident_density = d3.contourDensity().x(d => d[0]).y(d => d[1])

	var lum_scale = d3.scaleLinear().domain(d3.extent(incident_density, d => d.value)).range([10,100]);
	var chroma_scale = d3.scaleLinear().domain(d3.extent(incident_density, d => d.value)).range([90,3]);

		.attr("d", geo_generator)
		.attr('fill', '#eee')
		.attr('fill-opacity', '1.0')
		.attr('stroke', '#555')
		.attr('stroke-width', '0.4')

		.attr('fill', '#bbb')
		.attr('cx', d => d[0])
		.attr('cy', d => d[1])
		.attr('r', 2)
		.attr('fill-opacity', '0.4')

		.attr('d', d3.geoPath())
		.attr('fill', d => d3.hcl(0,chroma_scale(d.value),lum_scale(d.value)))
		.attr('stroke', 'none')