Skip to content

Instantly share code, notes, and snippets.

@thecristen
Forked from awoodruff/README.md
Created March 13, 2016 21:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thecristen/ca64ea61de781c20c7f4 to your computer and use it in GitHub Desktop.
Save thecristen/ca64ea61de781c20c7f4 to your computer and use it in GitHub Desktop.
Dot density map

This is an attempt at a slightly D3-ish way of drawing a dot density map on a canvas element. In short, each polygon is drawn (invisibly) with a unique color, then random points are thrown at polygon bounding boxes and the pixel color is used for a point-in-polygon test. This example draws one dot for every 2 people in central Boston census blocks, for a total of approximately 132,000 dots.

Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<html>
<head>
<title>Dot density map</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
</head>
<body>
<script>
var width = 1160,
height = 700;
// invisible map of polygons
var polyCanvas = d3.select("body")
.append("canvas")
.attr("width",width)
.attr("height",height)
.style("display","none");
// using this div to crop the map; it has messy edges
var container = d3.select("body")
.append("div")
.style({
"position": "relative",
"width": (width-200) + "px",
"height": (height-200) + "px",
"overflow": "hidden"
});
// canvas for dot map
var dotCanvas = container
.append("canvas")
.attr("width",width)
.attr("height",height)
.style({
"position": "absolute",
"top": "-100px",
"left": "-100px"
});
var projection = d3.geo.albers()
.rotate([71.083,0])
.center([0,42.3581])
.parallels([40,44])
.scale(880000)
.translate([width / 2, height / 2]);
var path = d3.geo.path().projection(projection);
var polyContext = polyCanvas.node().getContext("2d"),
dotContext = dotCanvas.node().getContext("2d");
var features;
d3.json( "blocks.json", function(error, blocks){
features = topojson.feature(blocks, blocks.objects.massblocks_central).features;
// draw the polygons with a unique color for each
var i=features.length;
while(i--){
var r = parseInt(i / 256),
g = i % 256;
drawPolygon( features[i], polyContext, "rgb(" + r + "," + g + ",0)" );
};
// pixel data for the whole polygon map. we'll use color for point-in-polygon tests.
var imageData = polyContext.getImageData(0,0,width,height);
// now draw dots
i=features.length;
while(i--){
var pop = features[i].properties.POP10 / 2; // one dot = 2 people
if ( !pop ) continue;
var bounds = path.bounds(features[i]),
x0 = bounds[0][0],
y0 = bounds[0][1],
w = bounds[1][0] - x0,
h = bounds[1][1] - y0,
hits = 0,
count = 0,
limit = pop*10, // limit tests just in case of infinite loops
x,
y,
r = parseInt(i / 256),
g = i % 256;
// test random points within feature bounding box
while( hits < pop-1 && count < limit ){ // we're done when we either have enough dots or have tried too many times
x = parseInt(x0 + Math.random()*w);
y = parseInt(y0 + Math.random()*h);
// use pixel color to determine if point is within polygon. draw the dot if so.
if ( testPixelColor(imageData,x,y,width,r,g) ){
drawPixel(x,y,0,153,204,255); // #09c, vintage @indiemaps
hits++;
}
count ++;
}
}
});
function testPixelColor(imageData,x,y,w,r,g){
var index = (x + y * w) * 4;
return imageData.data[index + 0] == r && imageData.data[index + 1] == g;
}
function drawPolygon( feature, context, fill ){
var coordinates = feature.geometry.coordinates;
context.fillStyle = fill || "#000";
context.beginPath();
coordinates.forEach( function(ring){
ring.forEach( function(coord, i){
var projected = projection( coord );
if (i == 0) {
context.moveTo(projected[0], projected[1]);
} else {
context.lineTo(projected[0], projected[1]);
}
});
});
context.closePath();
context.fill();
}
// there are faster (or prettier) ways to draw lots of dots, but this works
function drawPixel (x, y, r, g, b, a) {
dotContext.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
dotContext.fillRect( x, y, 1, 1 );
}
</script>
</body>
</html>
@thecristen
Copy link
Author

Note to self - try generating dots using turf.js, for example in this http://bl.ocks.org/wboykinm/41cf9893621d17ac893a

relevant bit:

// add the population of the feature
      var randomCount = block.properties.population;
      // increment up to the population count
      for (var i = 0; i < randomCount; i++) {
        // add a random point within the polygon's bbox
        var point = turf.random('points', 1, {
          bbox: blockBox
        });
        // check to see if the point is within the polygon itself;
        // if not, decrement the count and try again
        if (turf.inside(point.features[0], block) == false ) {
          i = i - 1;
          continue;
        }
        // if the point is inside the polygon, add it to the map
        L.geoJson(point, {
          pointToLayer: function (feature, latlng) {
            return L.circleMarker(latlng, markerStyle);
          }
        }).addTo(map);
      }

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