Tutorials

How to create an interactive dashboard in Mapbox

If you followed along with part one of our Mapbox tutorial, you should have a simple choropleth map on your hands. It’s a nice map, if a little skeletal. In this tutorial, we’re going to put some meat on them bones — so to speak.

In, and on top of, Mapbox, we’ll add a point layer with tooltips (i.e. popups), a legend, and a search function. These are all features that I created for a Mapbox dashboard in my story on rent bidding in Greater Boston, published in The Boston Globe earlier this year.

What am I looking at here?

Even a simple choropleth map needs a basic legend. How else will your reader know what the different color shades mean? Unfortunately, Mapbox itself does not have a built-in legend feature. However, you can create a custom legend using HTML, CSS and JavaScript. 

Here’s how.

First, you need to create containers for the various elements of your legend — things like the color gradient, text and numbers. You’ll want the element containing the legend to be nested inside the Mapbox div, like this:

In my case, I need a legend that will adjust based on the map layer being displayed. This requires JavaScript to dynamically populate the values of the legend, but, for simplicity’s sake, I’ll pretend you just need one static legend. In that case, you can hardcode all of the elements shown above.

With the elements in place, you’ll just need to apply CSS styling. The exact CSS you use will depend entirely on how you want the legend styled. It will also, in my experience, require some trial and error to get everything positioned exactly how you want. Here’s how mine looks:

Give me the dots!

Choropleth maps are great for displaying aggregated data, but what if we want to show the information in more detail? That’s exactly what I wanted to do for my rent bidding map. I knew that, if I were a reader with an extra minute to spare, I’d like to see specific rental units that rented for above, at, and below the asking price.

So, let’s do that.

To add a point layer, we’ll need a CSV file with latitude and longitude coordinates. If you already have a dataset with those coordinates, this step should be very simple. But, if you only have street addresses to go by, you’ll need to ✨geocode✨ the data. All this means is converting street addresses to lat and long coordinates.

There are a lot of ways to do this depending on the size of your data and your level of comfort writing code. If you have a very small number of locations, the simplest option is to search each location in Google Maps, right-click on the red pin, and copy the lat and long coordinates from there.

Of course, that approach can become quickly cumbersome. If you have a lot more addresses, the U.S. Census Bureau offers free batch geocoding here. Finally, if you’re already working with your data in something like a Jupyter Notebook, you can use a geocoding API. The Census Bureau offers a free one, but it is slower and occasionally less capable than paid alternatives from Google and Mapbox.

Here is what my CSV with lat and long coordinates looks like:

In the interest of protecting privacy, I not only removed the original address from this file, but I also added “jitter” to the lat and long coordinates, a way of adding some noise to the locations so that the dots are not exactly where the address is located.

Once your CSV is ready (make sure it includes information for tooltips), add it as a new layer in Mapbox Studio. In Studio, you can adjust the dot size based on the map’s zoom level. In my case, it isn’t helpful to show the dots when the reader is looking at the whole map, so I set the dots to only appear when you’re zoomed in past a certain threshold. 

DON’T MISS  Visualizing the destruction in Ukraine: A years-long project following satellite clues

I also want the dots to clearly indicate if the apartment was rented for below, at, or above the list price. To do this, I created a variable column called “BIDDING_DISCRETE,” in which each row has one of these values: “0” (below list), “1” (at list), or “2” (above list). Then, I select “Style across data range” and pick that column. From there, I assign blue to 0, grey to 1 and orange to 2, like this:

And don’t forget the tooltips

Where there are points there are tooltips — or at least there should be. Thankfully, Mapbox has a tooltip feature built in, along with a handy demo. To get popups, we’ll need some code to (1) prepare the content, (2) make it appear and (3) make it disappear. Here’s what my code for that first step looks like:

I’ll point out two things in the code above. First, I use in-line CSS styling. Is that the most efficient approach? Probably not. Did the map survive this brazen coding transgression? Absolutely. Second, you’ll see this repeating pattern of “feature.properties.SOME_VALUE.” What’s up with that? Well, since the price, number of bedrooms, etc is different for each place, we need the values in the popup to pull information from the point layer. The text inside the curly brackets acts as a placeholder for the value Mapbox will pull into the popup. This is how Mapbox wants me to do that.

A “feature” here is the map layer we’re pulling information from (more on this later). “Properties” are the individual columns of that layer. Then the final value is the specific column from which you want information.

All of the content is laid out in this function:

function showPopup(event) {

Now that we have the popup content laid out, we need to show the popup. The code to do that comes at the end of the function. Here’s what it looks like:

// Create and set the popup content
popup = new mapboxgl.Popup({
  offset: [0, -15],
  closeButton: false,
})
  .setLngLat(feature.geometry.coordinates)
  .setHTML(popupContent)
  .addTo(map2);

We also need to make sure that popups disappear too. Thankfully, that function is very simple. Here’s the full function:

function hidePopup() {
  // Remove the popup when the user stops hovering over the marker.
  if (popup) {
    popup.remove();
    popup = null; // reset the popup variable
  }
}

All this function does is check if a popup is currently showing, remove it if so, and reset the popup for the next dot the reader hovers over.

Finally, we need to call these functions. We do that with something called an event listener, which waits for an action to occur (like a mouse hovering over a point) and calls a function when it does.

// Attach event listeners for both mouseover and click/tap events
map2.on(“mouseenter”, “rent-bidding-point-data-jitter”, showPopup);

map2.on(“click”, “rent-bidding-point-data-jitter”, showPopup);

map2.on(“mouseleave”, “rent-bidding-point-data-jitter”, hidePopup);

Here is where we define the “feature” I mentioned earlier. In my case, that’s a map layer called “rent-bidding-point-data-jitter.” And voila! We have popups:

Let’s get personal

With just about any kind of data you map out, one of the first questions a reader is likely to ask is “What about where I live?” It’s a good question. News graphics can often abstract issues, but having something as simple as a search tool can create a more personal connection between the data and the reader, allowing them to see how an issue like rent bidding is impacting their ZIP Code.

DON’T MISS  How to keep the user in the picture, some design tips from Vox

The specifics of how you can accomplish this is quite cumbersome, but here’s the Cliff Notes version:

First, you want to create a container for your search box. Just like with the legend, this container should be placed inside the map container. Here’s what the HTML looks like for my search bar.

One of the key functions of the search tool is to zoom the map into the specific Zip Code that the reader searches. Doing so requires telling Mapbox what lat and long coordinates the map should zoom to, but what if you don’t have those coordinates? 

Well, another convenient feature of Mapbox is the free tier of its geocoding API. You can provide the API with a ZIP Code and it will return the lat and long coordinates for that Zip Code’s centroid, or center point. Then, you pass those coordinates to another of Mapbox’s convenient functions called “flyTo” which will move the map to the specific coordinates.

Here’s what that section of code looks like:

Great! That moves the reader to their ZIP Code. Now, we want to tell them stuff about it. As usual, we’ll need HTML elements to contain the information and some JavaScript to populate those elements. Because this feature is more complicated stylistically, I decided to create a mockup of it in Figma first:

There are different ways to go about making the real thing. In the interest of letting Mapbox handle things as much as possible, I added a GeoJSON file containing ZIP Code-specific information as a new layer in Mapbox studio and then used JavaScript to pull that info dynamically as a user searches.

If this approach seems familiar, it’s because I already did this with the popup content. But there’s an issue. If we wait to ask Mapbox for the Zip Code info only after a user starts a search, we’ll be looking at empty HTML containers while it fetches all of those different data points. The delay is only a split second, but it’s enough to make the whole process feel clunky. But there is a way!

Using async, we can load all of the Zip info as soon as the page loads and store it in a cache until we ask for that information for a specific county. With the information loaded, we can store each value in a variable like this:

And then replace the text of each HTML element with the dynamic information in those variables:

And here’s the final product of all our efforts:

<iframe src=”https://elijah-messmer.github.io/Rent-Bidding-Search-Tool/” 

        width=”100%” 

        height=”600px” 

        style=”border:none;”>

</iframe>

All the bits in between

I always used to be frustrated reading tutorials because I didn’t feel like they explained things step by step like I wanted (read: desperately needed) them to. Unfortunately, it’s just not possible, or at least worthwhile, going into that level of detail.
Your project is always going to diverge in significant ways than whatever examples you see online. So, while I didn’t cover all the details of how I built the Mapbox dashboard, I tried to cover the particular details that felt more universally helpful. That said, you can see the full code for the dashboard here if you’re interested.

Elijah Nicholson-Messmer

Leave a Reply

Your email address will not be published. Required fields are marked *

Get the latest from Storybench

Keep up with tutorials, behind-the-scenes interviews and more.