Version 4.21 (September 2021) of the ArcGIS API for JavaScript introduced the ability to control the drawing (or sorting) order of overlapping features using field values. Feature sorting is configured on the orderBy property of the FeatureLayer, CSVLayer, GeoJSONLayer, or OGCFeatureLayer.
layer.orderBy = [{
order: "descending", // or "ascending"
field: "Population",
// or alternatively...
valueExpression: "$feature.Poverty / $feature.Population"
}];
Controlling feature sort order is important if you want to establish a clear visual hierarchy of overlapping features within the same layer. Check out this excellent post by Zara Matheson that walks through several examples of configuring feature display order in the ArcGIS Online Map Viewer.
The following are common scenarios where sorting features can be useful:
- Render small symbols on top of large symbols to view all (or most) of the data at a glance
- Display features with large symbols on top of small symbols to hide irrelevant features
- Ensure more recent features are drawn on top of older features using a date field
Let’s explore a few examples.
Annual average daily traffic
The first example visualizes the annual average daily traffic (AADT) on Florida highways. This is a graduated symbol map of polyline features where the value of the AADT
field determines the width and color of the symbol.
Default order
By default, features are rendered in the order they are received by the client. Visually, feature order in this scenario may appear random.
layer.orderBy = null;
Notice how some smaller features, such as ramps and surface streets, are rendered on top of the high traffic highways.
Ascending order
In proportional symbol maps, controlling feature sort order allows you to establish a clear visual hierarchy. In many cases, seeing all the data at once is preferred. To maximize the amount of data in view, order features with small values on top of features with large values. This is done by sorting features based on the field used by the renderer in ascending
order.
layer.orderBy = [{
field: "AADT",
order: "ascending"
}];
Descending order
Perhaps you want to achieve the opposite scenario – render large features on top of small ones to ensure they always have the most prominence. This is accomplished by setting the order
property to descending
.
layer.orderBy = [{
field: "AADT",
order: "descending"
}];
Above and below visualizations
This example shows how you can leverage Arcade expressions in above and below (diverging data) visualizations to order features based on their symbol size.
Sorting by data value is different from sorting by symbol size
The orderBy
property currently does not allow you to sort by symbol size. Because small symbols typically represent small values and large symbols represent large data values, using orderBy
will usually produce a visualization that appears to order features by symbol size.
However, this doesn’t apply to graduated symbols representing data above and below a meaningful middle value. For example, the following expression calculates the change in the percentage of homes built with one bedroom in 2010 vs. 2020.
var oneBed2010 = $feature["pvph_1dor_1"];
var oneBed2020 = $feature["pvph_1dor"];
// Change in one-bedroom homes from 2010-2020
return oneBed2020 - oneBed2010;
Notice how the smallest symbols represent values close to a center value, and the features with very small or very large values have large symbols.
Check out the result if we sort the layer’s features based on the Arcade expression that matches the renderer.
const valueExpression = `
var oneBed2010 = $feature["pvph_1dor_1"];
var oneBed2020 = $feature["pvph_1dor"];
return oneBed2020 - oneBed2010;
`;
layer.orderBy = {
valueExpression,
// small values on top
order: "ascending"
};
All features in the red “below” category dominate the map because they represent negative values. If we flip the order to descending
, then the features in the “above” category will be given more importance. Since we want to see both above and below patterns clearly, both visuals fail.
Use Arcade to generate a sequence that orders by symbol size
To sort by symbol size in any above and below visualization, you must take the absolute value of the difference between the rendered value and the middle value (inflection point).
Abs(fieldValue - midValue)
In expressions that calculate change over time, the middle value is always zero. So you just have to take the absolute value of the final calculation, whether it represents total change or percent change.
const valueExpression = `
var oneBed2010 = $feature["pvph_1dor_1"];
var oneBed2020 = $feature["pvph_1dor"];
return abs(oneBed2020 - oneBed2010);
`;
layer.orderBy = {
valueExpression,
// small values on top
order: "ascending"
};
Now all small symbols render on top of features with larger symbols.
While it’s easier to see more points in this case, I’m more interested in the extremes. This communicates where the most change happened in the given time frame. To achieve this, keep the modified expression and switch the order to descending
.
const valueExpression = `
var oneBed2010 = $feature["pvph_1dor_1"];
var oneBed2020 = $feature["pvph_1dor"];
return abs(oneBed2020 - oneBed2010);
`;
layer.orderBy = {
valueExpression,
// Emphasizes the extremes (large symbols)
order: "descending"
};
Now we can clearly communicate that there was generally a larger decrease in one-bedroom homes, though some municipalities still had a significant increase.
Alternatives to visualizing overlapping features
The orderBy
property helps establish visual priorities. However, you may find that prioritizing feature order isn’t necessary or relevant in some cases.
For example, you can use opacity to see through a stack of features. This is nice because opaque areas clearly communicate relative density.
Alternatively, you can use hollow rings. Depending on feature density, this style may be easier to read than the former map.
If displaying all features isn’t required, you should also consider alternative methods for visualizing overlapping and dense datasets, such as clustering and heatmap.
Article Discussion: