In the Add legends guide, you saw how to add legends to a map using the getLegendsData
method, and how to display widgets using histogram expressions in the Add widgets guide. In this guide, you will build upon those concepts and learn how to obtain and display information in custom charts using the viewportHistogram
and globalHistogram
expressions and an external charting library.
In the previous guide histogram widgets were built with CARTO’s frontend framework Airship. As explained there, Airship widgets interact with CARTO VL directly, which means widgets are automatically connected to the map and can be configured accordingly. This guide takes a deeper dive into histogram expressions and the flexibility in CARTO VL to connect with external libraries. You will learn how to use histogram expressions to create bar charts for categorical data and histograms for numeric data using a Vancouver Trees dataset with the external charting library, Chart.js.
CARTO VL has two expressions to create histograms: viewportHistogram
and globalHistogram
. Both expressions return a list of values grouped by a column but differ in the way values are grouped. The viewportHistogram
expression returns a list based off of features that are in the viewport, while the globalHistogram
expression returns a list based on a data sample.
viewportHistogram
vs globalHistogram
The map below combines both viewportHistogram
and globalHistogram
expressions to compare the information returned for viewport vs global feature calculations. If you interact with the map, you’ll see how the bars for globalHistogram
remain static, while the ones for viewportHistogram
change depending on the features present in the viewport.
What you may notice is that if you zoom out, the viewportHistogram
chart doesn’t match the globalHistogram
chart. This is because the data returned for the globalHistogram
is a representative sample of the data. Therefore, the results may vary since we’re comparing the viewport data with a representative sample of the whole dataset.
Note:
If you need higher accuracy in your globalHistogram
, we recommend creating a custom query with a carto.source.SQL
source.
You can explore this step here
Now that you know the differences between viewport and global histograms, next, let’s look at using these expressions to draw charts.
First, let’s start with a basic bar chart that displays the count of trees planted on each street side category (describing whether a tree is planted on the odd, even, or middle side of a street) using the viewportHistogram
expression.
To start, define the source dataset and create a variable (@v_histogram
) in the viz
that will return the count and category information for the chart:
1
2
3
4
5
6
7
// Define the source
const source = new carto.source.Dataset('vancouver_trees');
// Define the visualization
const viz = new carto.Viz(`
@v_histogram: viewportHistogram($street_side_name)
`);
In order to draw a basic chart, start with a default configuration:
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
const chartOptionsDefault = {
animation: {
duration: 0
},
legend: {
display: false
},
scales: {
yAxes: [{
gridLines: {
drawBorder: false,
display: false
},
ticks: {
suggestedMin: 1,
beginAtZero: true,
display: false
}
}],
xAxes: [{
gridLines: {
drawBorder: false,
display: false
},
ticks: {
display: false
},
barPercentage: 0.9,
categoryPercentage: 1.0
}]
}
};
const chart = new Chart(ctx, {
type: 'bar',
options: chartOptionsDefault
});
In order for Chart.js to populate the bar chart with information from the Vancouver Trees dataset, it needs three arrays:
labels
: array of string values that indicate the label of each bar.data
: array of numeric values that indicate the height of each bar.backgroundColor
: array of colors that will be applied to each bar in the chart from left to right. If you assign a single color, all bars will be colored the same.The v_histogram.value
returns an array of { x, y }
objects where x
is the name of the street side category and y
is the amount of trees in that category. This is the information used to build the data
and labels
arrays for the bar chart. For backgroundColor
let’s assign a solid color (we will explore how to modify this later in the guide).
Since the values will be dynamically updated based on the viewport, the information should be returned once the layer is updated:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
layer.on('updated', () => {
// Get histogram data
const histogramData = layer.viz.variables.v_histogram.value;
// Chart.js set up
const labels = histogramData.map(elem => elem.x);
const data = histogramData.map(elem => elem.y);
const backgroundColor = '#00718b';
chart.data = {
labels,
datasets: [{
data,
backgroundColor
}]
};
chart.update();
});
On the resulting map, you will notice as you interact with it (zoom and pan) that the bars in the chart dynamically update based on the data in your current viewport. You can also hover over each bar in the chart to see the category name and count.
You can explore this step here
Next, let’s take a look at building a histogram to show the distribution of a numeric value, tree diameter.
For this case, let’s create a chart with six histogram bars that count the number of trees within each diameter range. To get this information, classify the diameter values into six buckets based on the viewport:
1
2
3
const viz = new carto.Viz(`
@v_histogram: viewportHistogram($diameter, [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]])
`);
On the resulting map, you will see a histogram with six bars for each diameter bucket and the count of trees in each one. Similar to the map above, the histogram bars dynamically update and can be hovered for more detailed information.
You can explore this step here
top()
In the first example you saw how to create a chart for all category values in the street side attribute. While that attribute has four unique values, there will be other times where you may want to summarize category values in your chart based on the data. For example, if we take an attribute like tree species name there are many categories, but in the chart, you only want to display the top five tree species and their count.
You can do this using the top
expression inside of the histogram expression:
1
2
3
const viz = new carto.Viz(`
@v_histogram: viewportHistogram(top($species_name, 5))
`);
As you can see in the map below, the result is a chart with the top 5 tree species in the data and all other values in an others bucket:
You can explore this step here
Note:
At this time, top
is the only expression available for use with histograms.
getJoinedValues
In all of the examples above, you will notice that the bar colors are a solid default color defined in the default chart properties.
What if you want to create a bar chart, and assign colors to each bar that correspond with the associated features on the map?
You can do this with a ramp
expression, the getLegendData()
method, and the getJoinedValues()
method which is part of viewportHistogram
and globalHistogram
.
The ramp
expression (@v_color
) is used to in two ways: to color the features on the map and to color the chart’s bars with the same colors using the getLegendData()
method:
1
2
3
4
5
const viz = new carto.Viz(`
@v_color: ramp($species_name, Vivid)
@v_histogram: viewportHistogram($species_name)
color: @v_color
`);
The ramp (@v_color
) and histogram (@v_histogram
) variables are then used to access to the getLegendData
and getJoinedValues
methods respectively:
1
2
3
4
5
6
7
8
// Save histogram variable
const histogram = layer.viz.variables.v_histogram;
// Save color variable
const color = layer.viz.variables.v_color;
// Get color ramp legend
const colorValues = color.getLegendData();
// Get histogram data
const histogramData = histogram.getJoinedValues(colorValues.data);
It is important to take into account that getJoinedValues
returns an array of { key, value, frequency }
elements sorted by frequency. In this case, the value contains a color because it is a color ramp.
These are the properties needed to build the chart’s bars where the labels come from the key
property, the data from frequency
, and the colors from value
:
1
2
3
4
// Chart.js set up
const labels = histogramData.map(elem => elem.key);
const data = histogramData.map(elem => elem.frequency);
const colors = histogramData.map(elem => elem.value);
You can explore this step here
But, what if you want to create a chart for a map that uses the top
expression? How can you tell the bar chart which colors are needed to display only the top five categories? The answer is to use the same operation in both the ramp and histogram expressions.
In this case, that means using top
for both the histogram (@v_histogram
), and the ramp (@v_color
):
1
2
3
4
5
const viz = new carto.Viz(`
@v_color: ramp(top($species_name, 5), Vivid)
@v_histogram: viewportHistogram(top($species_name, 5))
color: @v_color
`);
Since both share the same top
expression, it can be written to a variable (@top_five
). With some refactoring of the visualization, we have the equivalent as above:
1
2
3
4
5
6
const viz = new carto.Viz(`
@top_five: top($species_name, 5)
@v_color: ramp(@top_five, Vivid)
@v_histogram: viewportHistogram(@top_five)
color: @v_color
`);
The top
expression places all other values (that are not the top ones) into an ‘others’ bucket. In CARTO VL, by default, this bucket is labeled CARTO_VL_OTHERS
.
This can be overwritten by passing an options
object with the desired text set in othersLabel
and setting that same options object to both the getLegendData
and getJoinedValues
methods:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Save histogram variable
const histogram = layer.viz.variables.v_histogram;
// Save color variable
const color = layer.viz.variables.v_color;
// Get color ramp legend
const options = {
othersLabel: 'OTHER SPECIES'
};
const colorValues = color.getLegendData(options);
// Get histogram data
const histogramData = histogram.getJoinedValues(colorValues.data, options);
// Chart.js set up
const labels = histogramData.map(elem => elem.key);
const data = histogramData.map(elem => elem.frequency);
const colors = histogramData.map(elem => elem.value);
In the resulting map, both the map features and bars in the chart are colored by the top five tree species in the data. In addition, the bar for “others” is appropriately labeled. As in previous examples, the bars update with the appropriate information as you interact with the map and features in the viewport change.
You can explore this step here