Heatmaps are a very popular type of geo data visualization that are commonly used to display large amounts of location-based data. There are several libraries available for front-end developers that make it easy to create heatmaps. The most popular one is Heatmap.js.
It allows the creation of interactive heatmaps using HTML5 canvas. It can be used for any type of data, such as location-based data, user behavior data, or sales data. The library is open source and can be used for free. It’s used in such companies as Google, Airbnb, Waze, and Salesforce.
Besides Heatmap.js, there are several other alternatives, such as Leaflet-heat, and Google Maps Heatmap Layer, and it is even possible to implement that visualization with D3.js. But in this article, I would like to highlight the way of heatmap implementation using Deck.gl library. Before we take a look at that, let me explain what this library is about.
Deck.gl is a WebGL-powered data visualization library. It provides a variety of visualization layers, including scatterplots, line charts, heatmaps, and more, that can be combined to create complex, multi-layered visualizations. Deck.gl leverages the power of WebGL to render visualizations directly on the GPU, which gives it the ability to handle large amounts of data without sacrificing performance.
So, let’s implement a heatmap using Deck.gl. For that, I decided to create a simple React application and install MapBox to render maps. To be able to do that, we need to create an account at MapBox and get the API key. So, we can start with creating react app and installing the dependencies we need to render the heatmap.
Creating React application
npx create-react-app geo-viz
Installing MapBox and Deck.gl.
npm install react-map-gl @deck.gl/react
Also, we will need to install aggregation layers for deck.gl to be able to render heatmap layer over the map.
npm install @deck.gl/aggregation-layers
Once all is set, we can proceed with creating the map component. First, we need to create the DeckGL component and then pass it to its Map as a child.
export const Heatmap = () => {
return (
<DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} layers={layers}>
<Map reuseMaps mapboxAccessToken={API_KEY} mapStyle={MAP_STYLE} preventStyleDiffing={true} />
</DeckGL>
)
}
Let's take a closer look at the props we are passing to those components.
initialViewState
Here we pass configuration options to deck.gl layer, which defines the initial level of zoom, coordinates, etc. Here is an example:
export const INITIAL_VIEW_STATE = {
longitude: -73.75,
latitude: 40.73,
zoom: 9,
maxZoom: 16,
pitch: 0,
bearing: 0
};
controller
This prop allows us to enable viewport interactivity. If needed, we can add such options to specify behavior on zoom, scroll, double tap, etc.
layers
It’s exactly the layer data we want to render. And for that, we need to use aggregation layers we installed before, but let’s get back to it a bit later.
reuseMaps
As per docs, when it is set to true
, once a map component is unmounted, the underlying map instance is retained in memory.
mapboxAccessToken
Here we need to set a token, the one we get at our MapBox account.
mapStyle
That prop allows selecting the appearance of the map. For demo purposes, I used a black variant from basemaps carto cdn.
preventStyleDiffing
Enable diffing when mapStyle changes. If false, force a 'full' update, removing the current style and building the given one instead of attempting a diff-based update.
If we take a look at the initial layer now, this is what it looks like.
const layers = new HeatmapLayer({
data: "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/screen-grid/uber-pickup-locations.json",
getPosition: d => [d[0], d[1]],
getWeight: d => d[2],
id: "heatmap-layer",
intensity: 2,
radiusPixels: 30,
threshold: 0.03,
})
data
Each data entry is an array that contains three values: 2 of them are coordinates, and the third one is weight, which is needed for the heatmap.
getPosition, getWeight
Distribute coordinates points and weight on the map accordingly.
intensity, radiusPixels, threshold
These properties allow us to modify the way the heatmap looks.
Thats what the heatmap component looks like now:
import DeckGL from "@deck.gl/react";
import { Map } from 'react-map-gl';
import { HeatmapLayer } from '@deck.gl/aggregation-layers';
import {
API_KEY,
INITIAL_VIEW_STATE,
MAP_STYLE
} from './constants';
export const Heatmap = () => {
const layers = new HeatmapLayer({
data: "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/screen-grid/uber-pickup-locations.json",
getPosition: d => [d[0], d[1]],
getWeight: d => d[2],
id: "heatmap-layer",
intensity: 2,
radiusPixels: 30,
threshold: 0.03,
})
return (
<>
<DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} layers={layers}>
<Map reuseMaps mapboxAccessToken={API_KEY} mapStyle={MAP_STYLE} preventStyleDiffing={true} />
</DeckGL>
</>
)
}
And here is the output:
That’s a simple implementation of the heatmap using DeckGL, but it is capable of doing much fancier things and has a wide variety of data visualization to implement. This library itself has great potential in rendering complex data sets.
I’ve added some more functionality to the initial implementation of the app. I introduced two more components: header and widget. The header contains a title and a button to toggle the widget. The widget contains several select inputs that allow one to play around with heatmap layer intensity, radiusPixels, and threshold. Besides, one of the selects offers a change of data sets. Alternative data represents locations of earthquakes that happen for one month period. That representation is closer to a real case scenario.
Here is the link to the project on GitHub; feel free to clone it and play around with that. Let me know your thoughts in the comments. Hope this article was helpful. Cheers!