Search code examples
javascriptd3.jsvisualizationgeo

Using squares to represent counts in a map


This might be a simple question, but I have a map in d3 and I'd like to represent event-counts as squares.

Here's an example png of what I'm going for:

rough sketch

They're not aligned perfectly in the picture, but let's say I have a JSON:

[
    {city:'New York', count:3},
    {city:'Washington, D.C.', count:1},
    {city:'Austin', count:5},
    {city:'Havana', count:8}
] 

of counts that I'd like to represent as squares, preferably clustered in an orderly way.

I'm scratching my head on this — I think maybe a force-directed graph will do the trick? I've also seen this: http://bl.ocks.org/XavierGimenez/8070956 and this: http://bl.ocks.org/mbostock/4063269 that might get me close.

For context and set-up (I don't need help making the map, but just to share), here's the repo I'm using for the project: https://github.com/alex2awesome/custom-map, which shows the old way I was representing counts (by radius of a circle centered on each city).


Solution

  • does someone at least know what this might be called?

    The technical name of this in dataviz is pictogram.

    Here is a general code for plotting the rectangles, you'll have to change some parts according to your needs. The most important part is the calculation for the rectangles x and y position, using the modulo operator.

    First, let's set the initial position and the size of each rectangle. You'll have to set this according to your coordinates.

    var positionX = 5;
    var positionY = 5;
    var size = 5;
    

    Then, let's set how many rectangles you want (this, in your code, will be d.count):

    var count = 15;
    var gridSize = Math.ceil(Math.sqrt(count));
    var data = d3.range(count);
    

    Based on the count, we set the gridSize (just a square root) and the data.

    Now we plot the rectangles:

    var rects = svg.selectAll(".rects")
        .data(data)
        .enter()
        .append("rect");
    
    rects.attr("width", size)
        .attr("height", size)
        .attr("x", function(d,i){ return positionX + (i%gridSize)*(size*1.1)})
        .attr("y", function(d,i){ return positionY + (Math.floor((i/gridSize)%gridSize))*(size*1.1) })
        .attr("fill", "red");
    

    Here is a working snippet, using 15 as count (4, 9, 16, 25 etc will give you a perfect square). Change count to see how it adapts:

    var svg = d3.select("body")
    	.append("svg")
    	.attr("width", 50)
    	.attr("height", 50);
    	
    var count = 15;
    var size = 5;
    var positionX = 5;
    var positionY = 5;
    var gridSize = Math.ceil(Math.sqrt(count));
    var data = d3.range(count);
    
    var rects = svg.selectAll(".rects")
    	.data(data)
    	.enter()
    	.append("rect");
    	
    rects.attr("width", size)
    	.attr("height", size)
    	.attr("x", function(d,i){ return positionX + (i%gridSize)*(size*1.2)})
    	.attr("y", function(d,i){ return positionY + (Math.floor((i/gridSize)%gridSize))*(size*1.2) })
    	.attr("fill", "red");
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>