Skip to main content

Get started with Mapbox GL JS expressions

Prerequisite
Familiarity with front-end development concepts.

In this guide you'll learn how to write expressions in Mapbox GL JS to style custom data based on a data property and by zoom level.

Getting started

arrow-downDownload CSV

What are expressions?

In the Mapbox Style Specification, the value for any layout property, paint property, or filter may be specified as an expression. Expressions define how one or more feature property value and/or the current zoom level are combined using logical, mathematical, string, or color operations to produce the appropriate style property value or filter decision.

A property expression is any expression defined using a reference to feature property data. Property expressions allow the appearance of a feature to change with its properties. They can be used to visually differentiate types of features within the same layer or create data visualizations.

Uses

There are countless ways to apply property expressions to your application, including:

  • Data-driven styling: Specify style rules based on one or more data attribute.
  • Arithmetic: Do arithmetic on source data, for example performing calculations to convert units.
  • Conditional logic: Use if-then logic, for example to decide exactly what text to display for a label based on which properties are available in the feature or even the length of the name.
  • String manipulation: Take control over label text with things like uppercase, lowercase, and title case transforms without having to edit, re-prepare and re-upload your data.

Syntax

Mapbox GL JS expressions uses a Lisp-like syntax, represented using JSON arrays. Expressions follow this format:

[expression_name, argument_0, argument_1, ...]

The expression_name is the expression operator, for example, you would use '*' to multiply two arguments or 'case' to create conditional logic. For a complete list of all available expressions see the Mapbox Style Specification.

The arguments are either literal (numbers, strings, or boolean values) or else themselves expressions. The number of arguments varies based on the expression.

Here's one example using an expression to calculate an arithmetic expression (π * 32):

["*", ["pi"], ["^", 3, 2]]

This example uses a * expression to multiply two arguments. The first argument is 'pi', which is an expression that returns the mathematical constant pi. The second argument is another expression: a '^' expression with two arguments of its own. It will return 32, and the result will be multiplied by π.

Set up a map

Now that you've had an introduction to the uses and syntax for expressions, it's time to test it out yourself! Initialize a map with Mapbox GL JS and add the custom data as a circle layer.

Upload data

In this guide, you'll use a vector tileset to display data in your application. You can create a vector tileset by uploading the CSV you downloaded earlier to Mapbox Studio:

  1. Visit the Tilesets page in Mapbox Studio.
  2. Click New tileset.
  3. Select the CSV you downloaded at the beginning of this tutorial and click Confirm.
  4. A popover will appear in the bottom right showing the progress of your upload.
  5. Once the upload has succeeded, the tileset will be ready to use! Click on the name of the tileset in the popover, which will open the Tileset explorer.
  6. In the Tileset explorer, click on Share & use to find the tileset ID. You will use the tileset ID to add this tileset to your map in the next step.

Initialize a map with data

In a text editor, create a new index.html file and use the following code to initialize your map. You will need to:

  1. Make sure mapboxgl.accessToken is set equal to one of your access tokens.
  2. Replace your-tileset-id-here with the tileset ID for the tileset you created.
  3. Replace your-source-layer-here with the name of the source layer in your tileset.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Get started with Mapbox GL JS expressions</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
<link
href="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css"
rel="stylesheet"
/>
<style>
body {
margin: 0;
padding: 0;
}

#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN ';
const map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/light-v11', // stylesheet location
center: [-93.261, 44.971], // starting position [lng, lat]
zoom: 10 // starting zoom
});

map.on('load', () => {
map.addLayer({
id: 'historical-places',
type: 'circle',
source: {
type: 'vector',
url: 'mapbox://your-tileset-id-here'
},
'source-layer': 'your-source-layer-here'
});
});
</script>
</body>
</html>

Run your application in a browser, and you will see a light colored map centered on Minneapolis with black dots scattered across the city.

Write a property expression

Next, you'll write an expression to style the radius of each circle based on the age of each historic landmark. In this data file, provided by the City of Minneapolis's open data portal, the age of the historic landmark is not provided, but the year of construction is provided. You can use arithmetic to calculate the age based on the current year and style the circle-radius paint property based on the age.

Calculate the age of each landmark

Start by calculating the age of the landmark. Here's the expression you'll use:

['-', 2017, ['number', ['get', 'Constructi'], 2017]]

Use a '-' expression with two arguments. The first argument is a number (the current year, 2017). The second argument is another expression (a 'number' expression) with two arguments. This expression will convert the first argument to a number. If the first argument doesn't have a value, then the second argument, the current year 2017, will be used.

The first argument for the 'number' expression is yet another expression — this time a 'get' expression that retrieves the object property value of its only argument, 'Constructi'. ('Constructi' is the 'Construction_Date' from the original CSV file, but the name of the field was shortened in the upload process.) The value that is retrieved using 'get' is turned into a 'number'.

Ultimately, this expression results in the age of the landmark, essentially 2017 - year of construction.

Specify the circle radius

Use this expression inside the existing addLayer function in your code:

map.on('load', () => {
map.addLayer({
id: 'historical-places',
type: 'circle',
source: {
type: 'vector',
url: 'mapbox://your-tileset-id-here'
},
'source-layer': 'your-source-layer-here',
paint: {
'circle-radius': ['-', 2017, ['number', ['get', 'Constructi'], 2017]],
'circle-opacity': 0.8,
'circle-color': 'rgb(171, 72, 33)'
}
});
});

Refresh your browser and you'll see that the circle radius has changed dramatically. With the current expression, the radius of a circle representing a landmark that was built in 1950 would be 67 pixels. Next, you'll adjust the radius to be more appropriate in this context.

Adjust the circle radius

Wrap the expression in one other expression to make the size of the circles look better in this context. In this case, you'll divide the age of the landmark by 10.

map.on('load', () => {
map.addLayer({
id: 'historical-places',
type: 'circle',
source: {
type: 'vector',
url: 'mapbox://your-tileset-id-here'
},
'source-layer': 'your-source-layer-here',
paint: {
'circle-radius': [
'/',
['-', 2017, ['number', ['get', 'Constructi'], 2017]],
10
],
'circle-opacity': 0.8,
'circle-color': 'rgb(171, 72, 33)'
}
});
});

Refresh your browser and you'll see an updated map.

Add a zoom expression

The circles are still overlapping at the starting zoom level, 10.5, but they look good at higher zoom levels. You can use a zoom expression to address this issue. A zoom expression is any expression defined using ['zoom']. Such expressions allow the appearance of a layer to change with the map’s zoom level. Zoom expressions can be used to create the illusion of depth and control data density.

Use an 'interpolate' expression. An 'interpolate' expression produces continuous, smooth results by interpolating between pairs of input and output values ("stops"). In this case, use ['linear'] to interpolate linearly between a pair of stops slightly less than and slightly greater than the input. Then, specify that the radius should be the age of the landmark / 30 at zoom level 10 and age of the landmark / 10 at zoom level 13.

map.on('load', () => {
map.addLayer({
id: 'historical-places',
type: 'circle',
source: {
type: 'vector',
url: 'mapbox://your-tileset-id-here'
},
'source-layer': 'your-source-layer-here',
paint: {
'circle-radius': [
'interpolate',
['linear'],
['zoom'],
10,
['/', ['-', 2017, ['number', ['get', 'Constructi'], 2017]], 30],
13,
['/', ['-', 2017, ['number', ['get', 'Constructi'], 2017]], 10]
],
'circle-opacity': 0.8,
'circle-color': 'rgb(171, 72, 33)'
}
});
});
Note

If you've worked with property functions before, notice that interpolate expressions allow you to achieve the same effect as stop functions.

Refresh your browser and zoom into the map to see the resulting effect.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Minneapolis Landmarks</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
<link
href="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css"
rel="stylesheet"
/>
<style>
body {
margin: 0;
padding: 0;
}

#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN ';
const map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/light-v11', // stylesheet location
center: [-93.261, 44.971], // starting position [lng, lat]
zoom: 10.5 // starting zoom
});

map.on('load', () => {
map.addLayer({
id: 'historical-places',
type: 'circle',
source: {
type: 'vector',
url: 'mapbox://your-tileset-id-here'
},
'source-layer': 'your-source-layer-here',
paint: {
'circle-radius': [
'interpolate',
['linear'],
['zoom'],
10,
['/', ['-', 2017, ['number', ['get', 'Constructi'], 2017]], 30],
13,
['/', ['-', 2017, ['number', ['get', 'Constructi'], 2017]], 10]
],
'circle-opacity': 0.8,
'circle-color': 'rgb(171, 72, 33)'
}
});
});
</script>
</body>
</html>

Final product

You've styled custom data using expressions in Mapbox GL JS!

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Demo: Get started with Mapbox GL JS expressions</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
<link
href="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css"
rel="stylesheet"
/>
<style>
body {
margin: 0;
padding: 0;
}

#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
mapboxgl.accessToken = '{{MAPBOX_ACCESS_TOKEN}}';
const map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/light-v11', // stylesheet location
center: [-93.261, 44.971], // starting position [lng, lat]
zoom: 10.5 // starting zoom
});

map.on('load', () => {
map.addLayer({
'id': 'historical-places',
'type': 'circle',
'source': {
'type': 'vector',
'url': 'mapbox://examples.8ribcg3i'
},
'source-layer': 'HPC_landmarks-a88vge',
'paint': {
'circle-radius': [
'interpolate',
['linear'],
['zoom'],
10,
['/', ['-', 2017, ['number', ['get', 'Constructi'], 2017]], 30],
13,
['/', ['-', 2017, ['number', ['get', 'Constructi'], 2017]], 10]
],
'circle-opacity': 0.8,
'circle-color': 'rgb(171, 72, 33)'
}
});
});
</script>
</body>
</html>

Next steps

There are many other ways you can use expressions in Mapbox GL JS. For more information:

Was this page helpful?