CARTO VL

CARTO VL is a JavaScript library to create custom Location Intelligence applications over vector rendering.

Add legends

Maps that symbolize data without the necessary information to decode the symbols are not always effective in communicating their message. In this guide, you will explore how to enrich your visualizations with legends to aid interpretation by providing a visual explanation of point, line, or polygon symbols used on a map with a description of what they represent.

Overview

CARTO VL itself doesn’t provide the functionality to draw legends. Instead, it provides the functionality necessary to build them. What this means is that CARTO VL provides the data you need to create a legend, but drawing that data on the screen (in the form of a legend), is the responsibility of the application developer. The benefit of this is that you have more control over customizing legends for the needs of your specific application. Moreover, to build those visual components (and much more) you can use the power of our complementary library Airship.

If you completed the data-driven visualization guide, the map below will look familiar. In that guide, you learned how to symbolize feature properties with a ramp. In this guide, you will learn how to reference that ramp to access data for the legend, and then place the returned content on your map. At the end of this guide, we also provide a series of examples, that are meant to serve as the legend “building blocks” that you can take and begin to customize on top of for a variety of map types.

Getting started

The map below is the same one as above, a category map that symbolizes US rail accidents by reported weather conditions. Unlike the map above, you will notice that this map has a legend box with a title (“Rail Accidents by Weather”) in the right-hand corner, but is missing the legend information to help interpret what weather type each color on the map represents.

Let’s add that information!

To get started, copy and paste the code for this map and save it as accidents.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<html>

<head>
    <title>Rail accident weather | CARTO</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset="UTF-8">
    <script src="https://libs.cartocdn.com/carto-vl/v1.3.1/carto-vl.min.js"></script>

    <script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.js"></script>
    <link href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.css" rel="stylesheet" />

    <link href="https://carto.com/developers/carto-vl/v1.3.1/examples/maps/style.css" rel="stylesheet">
</head>

<body>

    <div id="map"></div>

    <aside class="toolbox">
        <div class="box">
            <header>
                <h1>Rail Accidents by Weather</h1>
            </header>
        </div>
    </aside>

    <script>

        const map = new mapboxgl.Map({
            container: 'map',
            style: carto.basemaps.darkmatter,
            center: [-96, 41],
            zoom: 4,
        });

        carto.setDefaultAuth({
            username: 'cartovl',
            apiKey: 'default_public'
        });

        const source = new carto.source.Dataset("railroad_accidents");

        const viz = new carto.Viz(`
            width: 7
            color: ramp($weather,[darkorange,darkviolet,darkturquoise])
            strokeWidth: 0.2
            strokeColor: black
        `);

        const layer = new carto.Layer('layer', source, viz);

        layer.addTo(map);

    </script>

</body>

</html>

Access data from ramp

To get the necessary information to populate the legend, you use the getLegendData() method. The getLegendData() method will reference the ramp expression where the symbology for your map is defined.

Take a look at the point styling for the accidents map. This is the styling that we want to bring into our legend and associate each legend item to each category type that we are symbolizing from the $weather property.

const viz = new carto.Viz(`
    width: 7
    color: ramp($weather, [darkorange, darkviolet, darkturquoise])
    strokeWidth: 0.2
    strokeColor: black
`);

Get legend data

In the map above, the ramp expression is the root expression of the styling property color. Therefore, it can be accessed directly with layer.viz.color.getLegendData().

Add the following code to your map right under layer.addTo(map) and before the closing </script> tag. Take a look through the inline comments describing the different steps to place the legend content when a map is loaded.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// When layer loads, trigger legend event
layer.on('loaded', () => {

    // Request data for legend from the layer viz
    const colorLegend = layer.viz.color.getLegendData();
    let colorLegendList = '';

    // Create list elements for legend
    colorLegend.data.forEach((legend, index) => {
        const color = legend.value;
        const rgba = `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;

        // Style for legend items
        colorLegendList +=
            `<li><span class="point-mark" style="background-color:${rgba};border: 1px solid black;"></span> <span>${legend.key}</span></li>\n`;
    });

    // Place list items in the content section of the title/legend box
    document.getElementById('content').innerHTML = colorLegendList;
});

Place legend data

If you load your map after the previous step, you will see that there is still no legend content!

That’s because we have to define a place for the last step of the process (document.getElementById('content').innerHTML = colorLegendList;) to place the information on the map once it is received.

To define where the content should be placed on the map, add a <section> in the title/legend box element of the page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Add title/legend box -->
<aside class="toolbox">
    <div class="box">
        <header>
            <h1>Rail Accidents by Weather</h1>
        </header>
        <!-- Add legend data -->
        <section>
            <div id="controls">
                <ul id="content"></ul>
            </div>
        </section>
    </div>
</aside>

Now, when you load the map, you will see the complete legend. You will also notice that the CARTO VL interpolated ramp colors (we only provided three colors for six categories) are also brought into the legend with the associated category name.

You can explore this map here

Overwrite defaults

In the map above, we are symbolizing all six weather categories in the data and therefore have an entry for each type in the legend. There are other times when there is symbology applied to some categories but not all of them. In this case, you will have an “others” legend item.

For example, the map below symbolizes only the top three weather conditions in the rail accident data. The legend labels the top three categories with all other categories labelled as CARTO_VL_OTHERS:

You can overwrite the default CARTO_VL_OTHERS value by setting the othersLabel option when using getLegendData:

1
2
3
const colorLegend = layer.viz.color.getLegendData({
    othersLabel: 'Other Weather'
});

With that change, the map labels other categories as “Other weather” in the legend:

You can explore this step here

Working with opacity

To assign opacity to your mapped features, you will usually work with the opacity and ramp expressions.

The styling below assigns a global 0.5 opacity to features, using some variables:

  • @myPalette: [darkorange, darkviolet, darkturquoise] for the colors assigned to each category
  • @myOpacity: 0.5

Note: The use of variables is explored in greater detail in the Add Widgets guide.

The variables are then used in the color property inside of an opacity expression:

const viz = new carto.Viz(`
    @myPalette: [darkorange, darkviolet, darkturquoise]
    @myOpacity: 0.5

    width: 7
    color: opacity(ramp($weather, @myPalette), @myOpacity)
    strokeWidth: 0.2
    strokeColor: black
`);

Next, you just need to use again the getLegendData() method, from the color property:

1
2
3
// Request data for legend from the layer variable myRamp
    const colorLegend = layer.viz.color.getLegendData();
    let colorLegendList = '';

At this point, if you refresh your map, you will see that not only the features on the map have the set opacity, but also the features in the legend:

You can explore this map here

More examples

View the source of the maps below to see how legends work for different map and geometry types.

Choropleth map

You can explore this map here

Categorical lines

You can explore this map here

Image markers

You can explore this map here

Unclassed points

You can explore this map here