Heatmap

Fatal car crashes in 2017 represented as a continuous heatmap. Areas with higher concentrations of crashes are yellow.

What is a heatmap?

A Heatmap renders point features as a raster surface, emphasizing areas with a higher density of points along a continuous color ramp.

Why are heatmaps useful?

Heatmaps can be used as an alternative to clustering, opacity, and bloom to visualize overlapping point features. Unlike these alternative techniques for visualizing density, you can use a heatmap to weight the density of points based on a data value. This may reveal a different pattern than density calculated using location alone.

Location-only heatmapData-weighted heatmap
classed with no label
Density of car crashes that resulted in a fatality (2017). Yellow areas indicate the highest density of crash incidents.
classed with no label
Density of fatal car crashes involving drunk drivers (2017). Yellow areas indicate the highest density of crash incidents that involved drunk drivers.

How a heatmap works

Heatmaps are created by setting a HeatmapRenderer instance to the renderer property of the layer. The HeatmapRenderer has several key properties. The blurRadius determines the area of influence surrounding a point. For example, if you set a blur radius of 10 pixels, each pixel within 10 pixels of a single point will be assigned an intensity value of 1. Pixels on the screen that fall outside that 10-pixel blur radius will have an intensity value of 0.

The intensity value of each pixel accumulates based on its proximity to multiple points. For example, a pixel within 10px of only a single point, will have a pixel intensity value of 1. A pixel within 10px of 10 points, will have a pixel intensity value of 10.

The colorStops in the renderer determine how to colorize pixels based on their pixel ratio (i.e. pixelIntensity / maxPixelIntensity). Pixels with an intensity value at or above the maxPixelIntensity have a pixel ratio of 1 and should be assigned the brightest color since these represent the hottest areas. The larger the maxPixelIntensity, the fewer hot spots will exist in the map.

Examples

Location heatmap

To create a heatmap, you must apply a HeatmapRenderer to a point layer. Set the renderer properties using the guidelines outlined above. Open this example in CodePen and adjust the blurRadius and maxPixelIntensity to see how these properties impact the heatmap.

Density of car crashes that resulted in a fatality (2017). Yellow areas indicate the highest density of crash incidents.
ArcGIS JS API
Use dark colors for code blocks
42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />

    <title>
      Fatal car crashes
    </title>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
    </style>

    <link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/themes/dark/main.css" />
    <script src="https://js.arcgis.com/4.22/"></script>

    <script>
      require([
        "esri/WebMap",
        "esri/layers/FeatureLayer",
        "esri/views/MapView",
        "esri/widgets/Legend"
      ], (WebMap, FeatureLayer, MapView, Legend) => {
        const url = "https://services1.arcgis.com/4yjifSiIG17X0gW4/arcgis/rest/services/FatalAccidents2017/FeatureServer";
        const layer = new FeatureLayer({
          title: "Fatal car accidents (2017)",
        const colors = ["rgba(115, 0, 115, 0)", "#820082", "#910091", "#a000a0", "#af00af", "#c300c3", "#d700d7", "#eb00eb", "#ff00ff", "#ff58a0", "#ff896b", "#ffb935", "#ffea00"];

        layer.renderer = {
          type: "heatmap",
          colorStops: [
            { color: colors[0], ratio: 0 },
            { color: colors[1], ratio: 0.083 },
            { color: colors[2], ratio: 0.166 },
            { color: colors[3], ratio: 0.249 },
            { color: colors[4], ratio: 0.332 },
            { color: colors[5], ratio: 0.415 },
            { color: colors[6], ratio: 0.498 },
            { color: colors[7], ratio: 0.581 },
            { color: colors[8], ratio: 0.664 },
            { color: colors[9], ratio: 0.747 },
            { color: colors[10], ratio: 0.83 },
            { color: colors[11], ratio: 0.913 },
            { color: colors[12], ratio: 1 }
          ],
          blurRadius: 10,
          maxPixelIntensity: 111,
          minPixelIntensity: 0
        };
        const map = new WebMap({
          basemap: {
            portalItem: {
              id: "466f3f43c231453c938c6776777b89e2"
          layers: [ layer ]
        const view = new MapView({
          container: "viewDiv",
          center: [-117.79621, 33.91474],
          scale: 1155581,
          constraints: {
            snapToZoom: false,
            minScale: 4622324,
            maxScale: 1155500
          new Legend({
        "top-right");
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

Data-weighted heatmap

This example modifies the previous heatmap to weight the surface by a data variable. The crashes layer used in this example has an attribute that tallies the number of drunk drivers involved in the incident. We can visualize where more drunk drivers are involved in fatal accidents by weighting by that field.

When a field is specified, each pixel's pixelIntensity is multiplied by the data value, so you need to adjust the maxPixelIntensity accordingly.

Density of car crashes involving drunk drivers that resulted in a fatality (2017). Yellow areas indicate the highest density of crash incidents.
ArcGIS JS API
Use dark colors for code blocks
42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70 70
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />

    <title>
      Fatal car crashes
    </title>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
    </style>

    <link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/themes/dark/main.css" />
    <script src="https://js.arcgis.com/4.22/"></script>

    <script>
      require([
        "esri/WebMap",
        "esri/layers/FeatureLayer",
        "esri/views/MapView",
        "esri/widgets/Legend"
      ], (WebMap, FeatureLayer, MapView, Legend) => {
        const url = "https://services1.arcgis.com/4yjifSiIG17X0gW4/arcgis/rest/services/FatalAccidents2017/FeatureServer";
        const layer = new FeatureLayer({
          title: "Fatal car accidents involving drinking (2017)",
        const colors = ["rgba(115, 0, 115, 0)", "#820082", "#910091", "#a000a0", "#af00af", "#c300c3", "#d700d7", "#eb00eb", "#ff00ff", "#ff58a0", "#ff896b", "#ffb935", "#ffea00"];

        layer.renderer = {
          type: "heatmap",

          field: "Number_of_Drinking_Drivers",

          colorStops: [
            { color: colors[0], ratio: 0 },
            { color: colors[1], ratio: 0.083 },
            { color: colors[2], ratio: 0.166 },
            { color: colors[3], ratio: 0.249 },
            { color: colors[4], ratio: 0.332 },
            { color: colors[5], ratio: 0.415 },
            { color: colors[6], ratio: 0.498 },
            { color: colors[7], ratio: 0.581 },
            { color: colors[8], ratio: 0.664 },
            { color: colors[9], ratio: 0.747 },
            { color: colors[10], ratio: 0.83 },
            { color: colors[11], ratio: 0.913 },
            { color: colors[12], ratio: 1 }
          ],
          blurRadius: 10,

          maxPixelIntensity: 21,

          minPixelIntensity: 0
        };
        const map = new WebMap({
          basemap: {
            portalItem: {
              id: "466f3f43c231453c938c6776777b89e2"
          layers: [ layer ]
        const view = new MapView({
          container: "viewDiv",
          center: [-117.79621, 33.91474],
          scale: 1155581,
          constraints: {
            snapToZoom: false,
            minScale: 4622324,
            maxScale: 1155500
          new Legend({
        "top-right");
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

Heatmap by scale

Heatmaps are only visually appropriate at a few scale levels. As the user zooms out, the heatmap will always appear hotter. As the user zooms in, the heatmap will appear colder. To prevent the user from viewing a heatmap that is too hot, you should set a minScale on the layer or as a view constraint.

As the user zooms in, the heatmap becomes irrelevant. Therefore, you should set a watch on the view to toggle the layer's heatmap to a simple renderer representing individual point locations.

Density of crimes classified as "fraud" in San Diego California. Zoom in and out on the map to view how the developer can restrict the user from seeing a heatmap that is too hot or too cold. As the user zooms in, the heatmap toggles off and displays the point locations as a simple renderer.
Set a constraint on map view scale to avoid hot heatmaps
Use dark colors for code blocks
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 41 42 43 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />

    <title>
      Create a scale-dependent visualization | Sample | ArcGIS API for
      JavaScript 4.21
    </title>

    <link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.22/"></script>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
    </style>

    <script>
      require([
        "esri/WebMap",
        "esri/views/MapView",
        "esri/widgets/Legend"
      ], (WebMap, MapView, Legend) => {
        const map = new WebMap({
          portalItem: {
            id: "559f46c1162d4a09901438d92148e53a"
        const view = new MapView({
          container: "viewDiv",
          map: map,
          constraints: {
            minScale: 1155582,
            snapToZoom: false
          }
        });
          new Legend({
            view: view
          "top-right"
        view.when().then(() => {
          // When the view is ready, clone the heatmap renderer
          // from the only layer in the web map
          const layer = view.map.layers.getItemAt(0);
          const heatmapRenderer = layer.renderer.clone();
          // The following simple renderer will render all points as simple
          // markers at certain scales
          const simpleRenderer = {
            type: "simple",
            symbol: {
              type: "simple-marker",
              color: "#c80000",
              size: 5
          // When the scale is larger than 1:72,224 (zoomed in passed that scale),
          // then switch from a heatmap renderer to a simple renderer. When zoomed
          // out beyond that scale, switch back to the heatmap renderer
          view.watch("scale", (newValue) => {
            layer.renderer = newValue <= 72224 ? simpleRenderer : heatmapRenderer;
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>
Switch to a location renderer after zooming beyond a scale threshold
Use dark colors for code blocks
81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 82 83 84 84 84 84 84 84 84 84 84 84 84 84
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />

    <title>
      Create a scale-dependent visualization | Sample | ArcGIS API for
      JavaScript 4.21
    </title>

    <link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.22/"></script>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
    </style>

    <script>
      require([
        "esri/WebMap",
        "esri/views/MapView",
        "esri/widgets/Legend"
      ], (WebMap, MapView, Legend) => {
        const map = new WebMap({
          portalItem: {
            id: "559f46c1162d4a09901438d92148e53a"
        const view = new MapView({
          container: "viewDiv",
          map: map,
          constraints: {
            minScale: 1155582,
            snapToZoom: false
          new Legend({
            view: view
          "top-right"
        view.when().then(() => {
          // When the view is ready, clone the heatmap renderer
          // from the only layer in the web map
          const layer = view.map.layers.getItemAt(0);
          const heatmapRenderer = layer.renderer.clone();
          // The following simple renderer will render all points as simple
          // markers at certain scales
          const simpleRenderer = {
            type: "simple",
            symbol: {
              type: "simple-marker",
              color: "#c80000",
              size: 5
          // When the scale is larger than 1:72,224 (zoomed in passed that scale),
          // then switch from a heatmap renderer to a simple renderer. When zoomed
          // out beyond that scale, switch back to the heatmap renderer
          view.watch("scale", (newValue) => {
            layer.renderer = newValue <= 72224 ? simpleRenderer : heatmapRenderer;
          });
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

API support

The following table describes the geometry and view types that are suited well for each visualization technique.

2D3DPointsLinesPolygonsMeshClient-sideServer-side
Clustering
Heatmap
Opacity
Bloom
Aggregation
Thinning11123
Visible scale range
Full supportPartial supportNo support
  • 1. Feature reduction selection not supported
  • 2. Only by feature reduction selection
  • 3. Only by scale-driven filter

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.