An introduction into Deck.gl

Uğur Ertaş
Webtips
Published in
9 min readMay 22, 2020

Deck.gl is an open-source data visualization framework made by Uber. With Deck.gl you can make awesome and interactive 3D visualizations like the one you see below in figure 1. Even though this framework was released 3 years ago it seems there aren’t a lot of tutorials to find on how to create your own Deck.gl visualization apart from the docs on the website. So today we will create a 3D data visualization from scratch to help you get a better understanding of how to use Deck.gl in your own personal projects.

figure 1: HexagonLayer DecK.GL
Figure 1: HexagonLayer Deck.gl

Setting up our development environment

For this tutorial, we are going to use React to create a project. The easiest way to set up a react project is through Create React App. If you haven’t installed create react app yet, check their docs to have the tools ready to start. We will create and run the project with the following commands:

npx create-react-app deckgl-datavizcd deckgl-dataviznpm start

The name of the folder can really be anything you like but this is what I choose for this tutorial. executing npm start should show a running page with a rotating react logo. congrats you now have a working react project we can use for our data viz. Deck.gl works in combination with react-map-gl which uses Mapbox to display a map. We will also use Axios to pull the data from an API and use d3-scale to scale the data so the columns on the map don’t grow to large. add the following dependencies to your package.json and execute npm install.

 “axios”: “0.19.2”,
“deck.gl”: “8.1.6”,
@deck.gl/core”: “8.0.15”,
@deck.gl/geo-layers”: “8.0.15”,
@deck.gl/layers”: “8.0.15”,
“mapbox-gl”: “1.7.0”,
“d3-scale”: “3.0.0”,
“react-map-gl”: “5.2.3”,

Displaying a Map

After the installation is done we can finally start building the app. Open app.js and replace all the code with the following:

import React from "react";
import DeckGL from "deck.gl";
import { StaticMap } from 'react-map-gl';
const MAPBOX_ACCESS_TOKEN = "pk.eyJ1IjoidWd1cjIyMiIsImEiOiJjazZvOXVibW8wMHR3M21xZnE0cjZhbHI0In0.aCGjvePsRwkvQyNBjUEkaw";
const mapStyle = "mapbox://styles/ugur222/ckab0shlf1zlt1hqhqqelaf90";
const INITIAL_VIEW_STATE = {
longitude: 12.8333,
latitude: 42.8333,
zoom: 4,
maxZoom: 16,
minZoom: 4,
pitch: 60,
bearing: 5
};
export default class App extends React.Component {
constructor(props) {
super();
}
componentDidMount() {
// will be used to fetch data from the api later
}
render() {
return (
<div>
}
<DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} >
<StaticMap mapStyle={mapStyle} mapboxApiAccessToken={MAPBOX_ACCESS_TOKEN} />
</DeckGL>
</div >
);
}
}

Obviously just copying and pasting code won’t help you understand what’s happening here so let’s go through it. First, we set the access-token in order to get access to a map from Mapbox. On the site of Mapbox they will explain how to get an access-token. The mapStyle property gets a style from Mapbox to display a map.

const MAPBOX_ACCESS_TOKEN = "pk.eyJ1IjoidWd1cjIyMiIsImEiOiJjazZvOXVibW8wMHR3M21xZnE0cjZhbHI0In0.aCGjvePsRwkvQyNBjUEkaw";const mapStyle ="mapbox://styles/ugur222/ckab0shlf1zlt1hqhqqelaf90";

After that, we initialize the settings for the map with INITIAL_VIEW_STATE. The zoom, longitude, and latitude are obvious but if you never worked with a 3D map before bearing and pitch will need some extra explanation. With the pitch property, you can set the angle of the map. If you change it to 0 it will give you a top-down view of the map. We use 60 in this example in order to get a clear view of the 3D columns which we will implement shortly. With the bearing property, you can decide in which direction you’re facing the map. So north is 0, east is 90, south is 180, and west is 270.

const INITIAL_VIEW_STATE = {
longitude: 12.8333,
latitude: 42.8333,
zoom: 4,
maxZoom: 16,
minZoom: 4,
pitch: 60,
bearing: 5
};

The last important part of the code is the Deck.gl wrapper on the bottom. Here we initialize the view-state which we declared above. The controller as the name suggests will enable or disable the controls of the map. Next with StaticMap we will add the map style and the access-token declared above.

<DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} ><StaticMap mapStyle={mapStyle} mapboxApiAccessToken={MAPBOX_ACCESS_TOKEN} />
</DeckGL>

Fetching Data From the NovelCOVID API

Now we will fetch data from the API in order to fill the 3D columns we are going to use later with meaning data. The NovelCOVID API supplies data for each country about the covid-19 outbreak. For this tutorial, we will keep it simple and only extract the active cases of each country. To get the data from the API we will use Axios. Now wit the API call added the code looks like below

...import axios from "axios";...
let data;
export default class App extends React.Component {
state = {};
constructor(props) {
super();
this.state = {
data: [],
};
}
componentDidMount() {
this.fetchData();
}
fetchData() {
axios.all([
axios.get('https://disease.sh/v2/countries?allowNull=false'),
]).then(axios.spread((World) => {
let data = World.data || [];
data = data.map(function (location) {
return {
active: location.active,
country: location.country,
continent: location.continent,
coordinates: [location.countryInfo.long, location.countryInfo.lat]
};
});
data = data.filter(location => (location.continent === "Europe"));
this.setState({ data: data });
})).catch((error) => {
console.log(error); return [];
})
}
render() {
const { data } = this.state;
console.log(data);
return (
<div>
}
<DeckGL initialViewState={INITIAL_VIEW_STATE} controller={true} ><StaticMap mapStyle={mapStyle} mapboxApiAccessToken={MAPBOX_ACCESS_TOKEN} />
</DeckGL>
</div >
);
}
}

I have used the array.filter function to only show all countries within Europe. If you would like to show only countries from another continent you can change it accordingly. You can even leave it out entirely to fetch every countries. Open your console in your browser and you should see an output of the array that is fetched from the API like below.

console.log output covid-19 api countries
Figure 2: console.log output covid-19 api countries

Now comes the tricky part where we have to add the data from the API to the 3D columns of the Deck.gl layer. For this, we will first need to create a separate component in which we store all the layers. Create a new file and name it deck.gl-layer.jsx. This will be a hook component which will only be used to load the layers in app.jsx. On the site of Deck.gl you can take a look at all the different layers they offer to plot data. For this tutorial, we will use the columnlayer since it is easy to work with and doesn't require a lot or complex code.

import { ColumnLayer } from “deck.gl”;
import { scaleLinear } from “d3-scale”;
export const RenderLayers = (props) => {
let maxActive, minActive;
const radiusColumns = 15000;
const { data} = props;
const value = data.map((a) => a.active);
maxActive = Math.max(…value);
minActive = Math.min(…value);
const elevation = scaleLinear([minActive, maxActive], [0, 20000]);
return [
new ColumnLayer({
id: “cases”,
data,
pickable: true,
extruded: true,
getPosition: d => d.coordinates,
diskResolution: 10,
radius: radiusColumns,
elevationScale: 50,
getFillColor: [255, 165, 0],
getElevation: d => elevation(d.active),
}),
];
}

We first initialize the max and min property for the elevation of the columns. Next, we create a radiusColumns property to set the size of the columns. You can change this later to your own needs when the data is loaded on the map. Next, we get the data through the props from app.jsx. Now we can set the min and max value dynamically with the data from the API and add them to the elevation property.

maxActive = Math.max(…value);
minActive = Math.min(…value);
const elevation = scaleLinear([minActive, maxActive], [0, 20000]);

Next, we create the layer. The id, in this case, can be anything just be sure that whenever you add a second layer they don’t have the same id. This is advised by Deck.gl for the following reason:

idis used to match layers between rendering calls. deck.gl requires each layer to have a unique id. A default id is assigned based on layer type, which means if you are using more than one layer of the same type (e.g. twoScatterplotLayers) you need to provide a custom id for at least one of them.

The data property is the data that we got from the API. In this case, our data property is named data so we don’t need to reference it. If your array is called let’s say collection you would need to write it like data:collection. The only important ones left are getPosition, getFillColor and getElevation. with position, we add the GPS coordinates to each column so all countries that are fetches get their own column. With fill color, we can decide the color. It’s important to note that the value needs to return an array with RGB value; it won't work with a hex code or a string. With scale elevation, we can set the height of the columns. We will use the active value of each country to accomplish this.

getElevation: d => elevation(d.active)

Now we need to import this layer to app.jsx like

import { RenderLayers } from “./deck.gl-layer.jsx”;

to add the layer to Deck.gl updated the Deck.gl wrapper by adding the renderLayers component to it like below

<DeckGL layers={RenderLayers({ data: data})} initialViewState={INITIAL_VIEW_STATE} controller={true} >

If you switch to your app it should look something like this.

Deck.GL column layer
Figure 3: Deck.gl column layer

Nice you made your own 3D data visualization. We are going to add one last thing before we are done. Let’s add a hover function which will show the active cases in each country when you hover over the corresponding column. First, we need to add an onHover function to the deck.gl-layers.jsx like below

const { data, onHover } = props;

Next add the function to the Columnlayer

new ColumnLayer({
id: “cases”,
data,
pickable: true,
extruded: true,
getPosition: d => d.coordinates,
diskResolution: 10,
radius: radiusColumns,
elevationScale: 50,
getFillColor: [255, 165, 0],
getElevation: d => elevation(d.active),
onHover,
}),

Now go back to app.jsx and let’s add the hover function to Deck.gl. let’s first create a hover object which will store all the data of the hovered column.

this.state = {
data: [],
hover: {
x: 0,
y: 0,
hoveredObject: null
}

};

Create a function to render the hover.

renderTooltip({ x, y, object, layer }) {
this.setState({ hover: { x, y, layer, hoveredObject: object } });
}

In the render function add the hover property to this.state so we don’t have to write this.state.hover every time.

const { hover, data } = this.state;

Before we access the data from the hover property we first need to add it to the Deck.gl wrapper like below.

<DeckGL ContextProvider={MapContext.Provider} layers={RenderLayers({ data: data, onHover: hover => this.renderTooltip(hover) })}initialViewState={INITIAL_VIEW_STATE} controller={true} >

Now we can access thehoveredObject to display the values in the tooltip.

return (
<div>
{hover.hoveredObject && (
<div style={{
position: "absolute",
zIndex: 1000,
background: "#ffffff",
pointerEvents: "none",
borderRadius: "5px",
left: hover.x,
top: hover.y
}} >
<ul className="hoveredObjectData">
<li><h4>{hover.hoveredObject.country}</h4></li>
<li>active cases: <span>{hover.hoveredObject.active.toLocaleString()}</span></li>
</ul>
</div>
)
}
</div >
);

I have added some inline styling to correctly display the data but you can change it to anything you want. I have added toLocatString() to the active value to display bigger numbers with a comma to make it look more clean.

Next add this CSS to the index.css file to get proper spacing in the tooltip.

.hoveredObjectData {
list-style-type: none;
margin:5px;
padding:5px;
}
h4 {
margin-bottom: 5px;
margin-top: 5px;
}

When that’s all set and done you should be able to see a tooltip when you hover over a column.

Figure 4: showing active cases and country name when hovered over a column

Conclusion

I hope you have a better understanding of how to use Deck.gl for your own projects. The next step is to see if you could perhaps add a click functionality to the columns to show an overlay that gives more detailed information of a country. For this detailed view, you could use a 2D graph library like Recharts or React-VIS. To get an idea of how it could be, take a look at my covid-19 visualization.

Resources used in this article…

--

--

Uğur Ertaş
Webtips

Front-end developer ugur-ertas.com #techNerd #environmentalist #vegetarian #dataviz 🇹🇷🇳🇱