ArcGIS Maps SDK for JavaScript

How and why to configure feature display order in web apps

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:

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;
Average annual daily traffic on Florida roads.
Average annual daily traffic on Florida roads. Dark, thick lines indicate high traffic roads, whereas light, thin lines are roads with less traffic. Some smaller road features draw on top of the larger ones. This may have the appearance of random rendering artifacts.

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"
}];
Sorting features based on the renderer's field in ascending order displays small features on top of large features, allowing you to view all or most of the data at once.
Sorting features based on the renderer's field in ascending order displays small features on top of large features, allowing you to view all or most of the data at once.

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"
}];
Sorting features based on the renderer's field in descending order displays large features on top of small features. This provides a clean visual hierarchy that promotes large, high-traffic highways over smaller roads. This may be desireable depending on the purpose of the map.
Sorting features based on the renderer's field in descending order displays large features on top of small features. This provides a clean visual hierarchy that promotes large, high-traffic highways over smaller roads. This may be desirable depending on the purpose of the map.

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;
Change in % of homes with no more than one bedroom (2010-2020).
Change in the percentage of homes with no more than one bedroom (2010-2020). Feature sorting is not specified in this map.

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"
};
Change in % of homes with no more than one bedroom (2010-2020).
Change in the percentage of homes with no more than one bedroom (2010-2020). Features with small values are rendered on top. Since small values have large sizes, this may not be the desired visual.

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.

Change in % of homes with no more than one bedroom (2010-2020).
Change in % of homes with no more than one bedroom (2010-2020). Features with small symbols are rendered on top, ensuring more data is visible to the user at this scale. However, the extreme features that may be more important are deemphasized.

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"
};
Change in % of homes with no more than one bedroom (2010-2020).
Change in the percentage of homes with no more than one bedroom (2010-2020). Features with large symbols are drawn on top of smaller symbols, giving them more prominence.

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.

Active COVID-19 cases on December 31, 2020. The graduated symbols in this map are very transparent, making it easier to see symbols that overlap in dense areas such as Los Angeles and New York City. The visualization style makes feature display order irrelevant here.
Active COVID-19 cases on December 31, 2020. The graduated symbols in this map are very transparent, making it easier to see symbols that overlap in dense areas such as Los Angeles and New York City. The visualization style makes feature display order irrelevant here.

Alternatively, you can use hollow rings. Depending on feature density, this style may be easier to read than the former map.

Total reported COVID-19 cases as of August 8, 2020. The size of each icon indicates the number of reported cases since January 22, 2020.
Total reported COVID-19 cases as of August 8, 2020. The size of each icon indicates the number of reported cases since January 22, 2020.

If displaying all features isn’t required, you should also consider alternative methods for visualizing overlapping and dense datasets, such as clustering and heatmap.

About the author

Kristian is a Principal Product Engineer at Esri specializing in data visualization. He works on the ArcGIS Maps SDK for JavaScript, ArcGIS Arcade, and Map Viewer in ArcGIS Online. His goal is to help developers be successful, efficient, and confident in building web applications with the JavaScript Maps SDK, especially when it comes to visualizing data. Prior to joining Esri, he worked as a GIS Specialist for an environmental consulting company. He enjoys cartography, GIS analysis, and building GIS applications for genealogy.

Connect:
1 Comment
Oldest
Newest
Inline Feedbacks
View all comments

Next Article

Drawing a Blank? Understanding Drawing Alerts in ArcGIS Pro

Read this article