Abstract State Map: Basic Recipe

d3 sandwich

Abstract State Map

Step #2: Create a Rectangle for Each State

In Step 1, we created an array of objects, one per state, containing the info needed to put each state on the map. In Step 2, the code below uses that info to create each state's rectangle.

Does the code to create the rectangles look weird? Yes, it does.

But don't sweat it! This code is a pattern you're going to use over and over in your D3 recipes. After you've used it a couple of times, it'll become second nature.

So for today, all you need is a rough idea of how the code works.

Before we dive in, let's spend a little time playing with the code; make a few changes and see how they affect the map.

    var width = 40, height = 40, cellsize = 39;
    svg_area = d3.select("svg")

    // Insert a rectangle for each state
    svg_area.selectAll("rect")
        .data(state_map)
      .enter()
      .append("rect")
        .attr("x", function(d,i)  { return d.col * width;})
        .attr("y", function(d,i) {return d.row * height;})
        .attr("width", cellsize)
        .attr("height", cellsize)
        .style("fill", "DarkRed");
  
Width
Height
Cell Size
Fill

FYI, if you play around with the height, width, and cellsize, you'll discover something interesting. The map looks like we draw white borders between the state rectangles, but it turns out we don't. If cellsize is smaller than height and width, that creates some whitespace between the rectangles that looks like a border. If you haven't played with it already, give it a try.

The Revenge of Your High School Math Teacher, Part 1

In high school, your math teacher probably made you learn geometry: rectangles, circles, etc. You probably wondered, why am I ever going to need any of this? Now you know: to create cool data visualizations in D3!

The good news: the math you're going to need is really simple.

The first thing we need to know: what info do we need to create a rectangle?

To get a feel for it, let's play:
Width
Height
Fill
X
Y

Try changing the rectangle's height, width, X, Y, and fill/color and see what happens.

Revenge of Your High School Math Teacher, Part 2

We've learned how to create a rectangle. Now we need to use math to do 2 more things:

  1. Put a bunch of rectangles on the page, each with its own starting point (i.e., X and Y)
  2. Create some space between each of the rectangles, which makes it looks like there is a white border between the rectangles
{ Using the variables height, width, and cell size is confusing. What we need to do is something like rectangleSize and something else. What are the best names for these? And then how is the best graphic way to give someone a feel for how the math works?}
Notes Dump

Before you start playing with the rectangles , take a look at X and Y. Notice how each X and Y is 40 more than the last one? That's because each rectangle's height and width is 40.

State X Y
ME
VT
NH
WA
ID
NY

Now that we know that to translate a state's row into X, we want to multiply the row by 40 and we want to do the same math for Y, how do we tell the computer? In math class, they use what they call a formula:


Putting It All Together

Now that we know the formulas we need, how do we put it all together? How do we use the data we have in stateMap to create each state's rectangle?

Let's go back to the code that makes it happen. We'll explain the details in a minute. Here's how you read it:

Create a 960 x 400 pixels SVG area where we can put our rectangles,
and create a variable called svgArea that selects/points to it.


For each item in stateMap,
add ("append") a rectangle with the following values:

  • x = the state's column * width
  • y = the state's row * height
  • width = cellsize
  • height = cellsize
  • fill, aka color = DarkRed
    <svg width="960" height="400"></svg>        <!-- SVG graphics will go here  -->
    ...

    svg_area = d3.select("svg")

    // Insert a rectangle for each state
    svg_area.selectAll("rect")
        .data(state_map)
      .enter()
      .append("rect")
        .attr("x", function(d,i)  { return d.col * width;})
        .attr("y", function(d,i) {return d.row * height;})
        .attr("width", cellsize)
        .attr("height", cellsize)
        .style("fill", "DarkRed");
  

The lines creating and selecting the SVG area are pretty straightforward. But there are a lot of concepts in the rest of the code even though it's only a few lines long, so let's break it into two parts.

The Second Half: The Code to Describe the Rectangles

The second half of the code, which describes how the rectangles should be created, is easier to wrap your head around than the first half. So let's start there:

Because each rectangle has the same width, height, and fill/color, setting them is pretty straightforward.

        .attr("width", cellsize)
        .attr("height", cellsize)
        .style("fill", "DarkRed");
  

But each rectangle needs to have a different X and Y. How do we tell D3 the formulas we want to use? By using the weird looking "function(d,i)".

"function(d,i)" is shorthand. It tells D3, as you loop through each of the items in stateMap, give me two variables:

  • d: the item you are on – e.g., WA
  • i: the number of the item you're on -- 0, 1, 2, 3 (computers like to start counting from 0)

In this case we only care about d, the item. We can use d to get to the info we have about each state: "d.col" is the state's column.

        .attr("x", function(d,i)  { return d.col * width;})
        .attr("y", function(d,i) {return d.row * height;})
  

Is It weird looking the first time you see it? Yes. But don't sweat it: after the fifth or sixth time you seen it, it'll seem second nature.

The Select-Enter-Append Pattern

Now let's explore the first half of the code.

I found this pattern – the "select-bind-enter-append" pattern – the hardest thing to wrap my head around when learning D3. The bad news: you have to learn it at the beginning, because it's used absolutely everywhere. The good news: at the beginning, you don't need to understand the details, just what the pattern is telling you.

Here's literally what the code says:

  • Select every rectangle that's in the SVG area.
  • Bind the data in stateMap to those rectangles.
  • If there aren't enough rectangles for all the data, create a new rectangle for the remaining data.
    svg_area.selectAll("rect")
        .data(state_map)
      .enter()
      .append("rect")
  

You're probably thinking, why do I start by selecting all the rectangles in the SVG area when I know there's nothing there?

There are some key key technical reasons why the man who created D3 decided to do it this way. Bottom line: you could care less. All you need to know is:

Recap: D3's "Saute until the Onions Start to Brown

Ouch! That was way too many things to have to learn when you're just getting started. We're sorry about that. Like we said at the beginning of this step, it's what makes the first few baby steps of D3 so rocky.

But even though the code looks a little weird, you did an amazing amount in just a few lines of code. In the long run, that's part of the payoff -- and that's why you're going to see this pattern over and over as you make D3 recipes.

And after you've done a couple D3 visualizations, it'll become as natural to you as the instructions "sauté the onions until they start to brown" are to an experienced cook.

Speaking of which, in the next step, you'll get to use this pattern one more time.

Up Next: Recipe Step #3: Using stateMap to create the names on the Map