ArcGIS Maps SDK for JavaScript

8 ways to style point clusters on the web

Version 4.25 of the ArcGIS API for JavaScript (ArcGIS JS API) introduced the ability to override default styles for point clustering. You can now do the following:

These have been some of the most popular enhancement requests by our user communities.

Earthquakes clustered along the Aleutian Islands.
The latest version of the ArcGIS API for JavaScript allows you to represent clusters as symbols distinct from individual features.

Let’s take a look at 8 examples for styling clusters — beginning with default styles, and ending with the new capabilities.

Default cluster styles

By default, clusters are styled so they represent a summary of the variable used to style individual points within the cluster. Typically, the size of each cluster varies depending on the count of points it represents. The following are just a few examples of how clusters are styled by default.

1. Simple renderer

The SimpleRenderer allows you to style a layer so that each point has the same symbol. This simply communicates where data points exist with no additional information. When clustering is enabled on a layer with this style, each cluster is represented with the same symbol as individual points. The size of the cluster indicates the number of points it represents.

Simply enable clustering on the featureReduction property of the layer, and the cluster will be automatically styled with the same symbol as the layer’s renderer.

const layer = new GeoJSONLayer({
   renderer: {
     type: "simple",
     field: "mag",
     symbol: {
       type: "simple-marker",
       size: 4,
       color: "#c86558",
       outline: {
         color: "rgba(0, 0, 0, 0.3)",
         width: 0.5
       }
     }
   },
   featureReduction: {
     type: "cluster"
     // ...other clustering properties
   }
   // ...other layer properties
 });
 

View the live app
Explore the code

Earthquakes along the Ring of Fire in November 2022.
Earthquakes visualized as point clusters along the Ring of Fire in November 2022. The size and label of the cluster indicates the total number of earthquakes in the area. All clusters reuse the symbol representing individual earthquakes. Non-clustered earthquake locations are the unlabeled small points. Data source: USGS.

2. Continuous color

A color visual variable allows you to visualize a numeric variable along a continuous color ramp. When this is applied to a clustered layer, the average value of the field or expression in the color variable is used to color the cluster according to the same color stops defined in the layer’s renderer.

Again, for clusters to pick up this style, all you need to do is enable clustering and the layer’s renderer automatically applies the appropriate color.

const layer = new GeoJSONLayer({
  renderer: {
    type: "simple",
    symbol: {
      type: "simple-marker"
    },
    visualVariables: [{
      type: "color",
      field: "mag",
      legendOptions: {
        title: "Average magnitude"
      },
      stops: [
        { value: 2, color: "#00619b" },
        { value: 3, color: "#50a7da" },
        { value: 4, color: "#ffd8bf" },
        { value: 5, color: "#f89960" },
        { value: 6, color: "#b35116" }
      ]
    }]
  },
  featureReduction: {
    type: "cluster"
    // ...other clustering properties
  }
  // ...other layer properties
});

View the live app
Explore the code

Earthquakes colored by average magnitude along the Ring of Fire.
Earthquakes along the Ring of Fire in November 2022. Each earthquake is colored based on its magnitude. Clusters are colored based on the average magnitude of earthquakes in the cluster. While the west coast of North America experiences many more earthquakes than eastern Asia, the earthquakes tend to be smaller in magnitude. Data source: USGS.

3. Predominance

When a UniqueValueRenderer is used to style a clustered point layer, the symbol of the predominant (or most common category) is used to represent the cluster by default.

const layer = new FeatureLayer({
   renderer: {
     type: "unique-value",
     field: "Complaint_Type",
     uniqueValueInfos: [
       // defines symbols for representing
       // unique categories or values
     ]
   },
   featureReduction: {
     type: "cluster"
     // ...other clustering properties
   }
   // ...other layer properties
 });
 

View the live app
Explore the code

311 calls clustered in New York City.
Vehicle-related 311 incidents reported in New York City (2015). Each point is colored based on the incident type. Clusters are represented with the color of the predominant type in the cluster.

Custom styles

While automatically styling clusters based on a layer’s renderer is useful, there are scenarios where you may want to override this behavior. These are the new capabilities introduced at version 4.25 of the ArcGIS API for JavaScript.

4. Distinct cluster symbol

Sometimes a summary of the features within the cluster is irrelevant and you just want to see a binary view of whether a symbol in the map represents an aggregate of points, or an individual feature.

In this case, you can define a symbol on the FeatureReductionCluster instance. This will style each cluster with the same symbol. The size of each cluster will continue to depend on the count of points it represents. Individual points will retain the symbology as defined in the layer’s renderer, regardless of renderer type.

const layer = new GeoJSONLayer({
   // individual points show as triangles
   renderer: {
     type: "simple",
     symbol: {
       type: "simple-marker",
       size: 8,
       color: "#69dcff",
       style: "triangle",
       outline: {
         color: "rgba(0, 139, 174, 0.5)",
         width: 1
       }
     }
   },
   // clusters show as circles with thick outlines
   featureReduction: {
     type: "cluster",
     symbol: {
       type: "simple-marker",
       style: "circle",
       color: "#69dcff",
       outline: {
         color: "rgba(0, 139, 174, 0.5)",
         width: 6
       }
     }
     // ...other clustering properties
   }
   // ...other layer properties
 });
 

View the live app
Explore the code

Earthquakes along the Aleutian Islands, Alaska.
Earthquakes that occurred in the Aleutian Islands, Alaska in November 2022. Individual earthquakes are symbolized with a blue triangle. Clusters of earthquakes are represented with circles of various sizes. Sometimes visualizing aggregates with a distinct symbol provides more clarity to the map reader. Data source: USGS.

Cluster renderers

For scenarios where you want to customize the symbol of the clusters, but vary the symbol properties dynamically based on a data value, you’ll need to create a cluster renderer. This requires you define at least one aggregate field (or reuse an auto-generated field) to use in the renderer.

Aggregate fields

Creating any custom renderer for clusters requires you to define at least one aggregate field in the fields property of FeatureReductionCluster. Aggregate fields are defined in the same manner they are for binning visualizations. An aggregate field must be defined with a layer field, statistic type, and be given a name.

The snippet below demonstrates how to create an aggregate field that sums the total number of fatalities reported in a field for all features included in a cluster. The resulting field may be used in the cluster’s renderer, popupTemplate, or labels.

const layer = new FeatureLayer({
  featureReduction: {
    type: "cluster",
    fields: [
      new AggregateField({
        name: "SUM_KILLED",
        onStatisticField: "NUMBER_OF_PERSONS_KILLED",
        statisticType: "sum"
      })
    ]
    // SUM_KILLED can be used in
    // other clustering properties:
    // ...renderer
    // ...popupTemplate
    // ...labelingInfo
  }
  // ...other layer properties
});

5. Size based on sum

By default, cluster sizes represent the total number of features in the cluster, and are controlled with clusterMinSize and clusterMaxSize. This is still true when defining any custom renderer on the FeatureReductionCluster instance.

You can override this default sizing behavior by setting any size visual variable in the cluster renderer. For example, you can use a custom renderer to size clusters based on the sum of a numeric attribute, rather than the average value (the default behavior when the underlying layer has a size variable assigned to the renderer).

To do this, you need to define an aggregate field based on the sum of a numeric attribute of the layer, then use it in a SizeVariable within any renderer type. This example shows how to sum clusters based on the total number of people living within each cluster. Note how the population_total aggregate field is referenced in the cluster renderer’s size variable.

const markerSymbol = {
  type: "simple-marker",
  style: "circle",
  color: "green",
  size: 4
};

const layer = new FeatureLayer({
  renderer: {
    type: "simple",
    symbol: markerSymbol
  },
  featureReduction: {
    type: "cluster",
    // define aggregate field for population sum
    fields: [{
      name: "population_total",
      onStatisticField: "POP",
      statisticType: "sum"
    }],
    renderer: {
      type: "simple",
      symbol: markerSymbol,
      visualVariables: [
        {
          type: "size",
          // reference aggregate field in size variable
          field: "population_total",
          stops: [
            { value: 0, size: 8 },
            { value: 100, size: 12 },
            { value: 10000, size: 18 },
            { value: 50000000, size: 48 }
          ]
        }
      ]
    }
    // ...other clustering properties
  }
  // ...other layer properties
});

View the live app
Explore the code

Population of world cities as clusters.
The population of major cities as clusters. Each cluster is sized based on the total population of cities included in the cluster. Data source: ArcGIS Living Atlas of the World.

6. Color based on any aggregate field

You can reference aggregate fields in any visual variable type and use it to override the default cluster style. In this scenario, the size of the cluster is automatically applied based on cluster count (and controlled by clusterMinSize and clusterMaxSize). The color is varied based on an aggregate field that calculates the ratio of injuries in car crashes to the number of crashes.

const layer = new FeatureLayer({
  renderer: {
    type: "simple",
    label: "Crash location",
    symbol: {
      type: "simple-marker"
    }
  },
  featureReduction: {
    type: "cluster",
    fields: [{
      name: "AVG_MOTORIST_INJURED",
      onStatisticField: "NUMBER_OF_MOTORIST_INJURED",
      statisticType: "avg"
    }],
    // Override default cluster symbol with
    // aggregate info not included in layer's renderer
    renderer: {
      type: "simple",
      symbol: {
        type: "simple-marker"
      },
      visualVariables: [
        {
          type: "color",
          field: "AVG_MOTORIST_INJURED",
          legendOptions: {
            title: "% of motorists injured"
          },
          stops: [
            { value: 0, color: "#d7e1ee", label: "No injuries" },
            { value: 0.12, color: "#cbd6e4" },
            { value: 0.25, color: "#b3bfd1", label: "25%" },
            { value: 0.37, color: "#c86558" },
            { value: 0.5, color: "#991f17", label: ">50%" }
          ]
        }
      ]
    }
    // ...other clustering properties
  }
  // ...other layer properties
});

View the live app
Explore the code

Vehicle crashes in New York City (2020).
Vehicle crashes in New York City (2020) represented as clusters. The size of the cluster indicates the total number of crashes. The color indicates the ratio of injured persons to each incident. Data source: NYC OpenData.

7. Binary (includes vs. excludes)

You can also style clusters based on whether they include or exclude high priority features. This example colors clusters red if they include at least one fatality. All other clusters without fatalities are colored beige. You can achieve this visualization by setting a ClassBreaksRenderer with two breaks on the FeatureReductionCluster.renderer property. One break indicates a total of zero, and the other break includes features with a sum of any number larger than zero.

const includedColor = "#de2c30";
const excludedColor = "#ffdaa6";

const layer = new FeatureLayer({
  featureReduction: {
    type: "cluster",
    fields: [
      // sum all fatalities in the cluster
      new AggregateField({
        name: "SUM_KILLED",
        onStatisticField: "NUMBER_OF_PERSONS_KILLED",  // layer field
        statisticType: "sum"
      })
    ],
    renderer: {
      type: "class-breaks",
      field: "SUM_KILLED",  // aggregate field
      legendOptions: {
        title: "Car crashes"
      },
      classBreakInfos: [{
        minValue: 0,
        maxValue: 0,
        label: "No fatalities",
        symbol: {
          type: "simple-marker",
          color: excludedColor,
          outline: {
            color: "rgba(153, 31, 23, 0.3)",
            width: 0.3
          }
        }
      }, {
        minValue: 1,
        maxValue: 99999999999,
        label: "Includes fatalities",
        symbol: {
          type: "simple-marker",
          color: includedColor,
          outline: {
            color: "rgba(153, 31, 23, 0.3)",
            width: 0.3
          }
        }
      }]
    }
    // ...other clustering properties
  }
  // ...other layer properties
});

View the live app
Explore the code

Vehicle crashes in New York City (2020) clustered.
Vehicle crashes in New York City (2020) represented as clusters. The size of the cluster indicates the total number of crashes. Red clusters indicate at least one fatality occurred within the cluster. Data source: NYC OpenData.

8. Clusters as pie charts

By default, clusters for layers rendered with a UniqueValueRenderer will render using the symbol of the predominant category in the cluster. However, this visualization can be misleading as the predominant category often doesn’t indicate a majority.

comparison between pie charts and predominant symbols in point clusters
By default clusters of categorical data are represented with the predominant category in the cluster (left). However, using pie charts (right), shows that predominance may skew the interpretation of the data since the predominant category may not represent a majority of features.

For this reason, you may want to show more detail about the categories inside the cluster using a PieChartRenderer. To do this, you need to define a field for each category that returns the sum of the features belonging to that category. This can be done by writing a one-line Arcade expression for each category. You should also be careful to match the colors of the pie slices with the colors of the unique value infos of the layer’s renderer.

Because this can be a tedious process, we provide the createRendererForClustering function to generate a pie chart renderer along with the required fields to apply to the FeatureReductionCluster instance.

layer.renderer = {
   type: "unique-value",
   field: "Complaint_Type",
   uniqueValueInfos: [
     // defines symbols for representing
     // unique categories or values
   ]
   // ...other layer properties
 },

 const { renderer, fields } =
   await pieChartRendererCreator.createRendererForClustering({
     layer,
     shape: "donut"
   });

 layer.featureReduction = {
   type: "cluster",
   fields,
   renderer
   // ...other clustering properties
 };
 

This function allows you to generate a donut chart or a fully filled in pie chart. The donut chart is nice because you can fit a label inside the donut hole, but the pie may be easier to read.

View the live app
Explore the code

Vehicle-related 311 incidents in New York City (2015).
Vehicle-related 311 incidents in New York City (2015) represented as point clusters. Each cluster is a donut chart where each color on the chart indicates an incident type. This is effective for placing the the count of the cluster in the donut hole, but the overall pattern may be more difficult to interpret.
Vehicle-related 311 incidents in New York City (2015) represented as point clusters. Each cluster is a pie chart where each color on the chart indicates an incident type. This is probably easier to read than a donut chart, though the abundance of color can still overwhelm the user.

Conclusion

The latest version of the ArcGIS JS API gives you full control over styling clusters in a variety of ways. While having this degree of control is nice, keep in mind that most of the time the styles automatically generated based on the layer’s renderer will be the best visualization option.

Check out all the clustering samples in the ArcGIS JS API documentation to learn more.

About the author

Kristian is a Senior Product Engineer at Esri. His work focuses on data visualization and Arcade integration in the ArcGIS Maps SDK for JavaScript. His goal is to help developers be successful, efficient, and confident in building web applications with the 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:
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments

Next Article

Working with Enhanced Contrast basemaps to improve accessibility.

Read this article