Hide Table of Contents
View Point clustering sample in sandbox
Point clustering

Description

Point clustering has been implemented in this sample with a custom layer named extras.ClusterLayer. This custom layer subclasses esri.layers.GraphicsLayer. Symbology for clusters is managed using renderers. A class breaks renderer is recommended. For constructor options, public properties as well as layer methods, please refer to the comments in the ClusterLayer.js file.

If using IIS as your web server, please configure your IIS instance to serve JSON.

Code

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
    <title>Cluster</title>
    <link rel="stylesheet" href="https://js.arcgis.com/3.20/dijit/themes/tundra/tundra.css">
    <link rel="stylesheet" href="https://js.arcgis.com/3.20/esri/css/esri.css">
    <style>
      html, body { height: 100%; width: 100%; margin: 0; padding: 0; }
      #map{ margin: 0; padding: 0; }

      /* center the image in the popup */
      .esriViewPopup .gallery { margin: 0 auto !important; }
    </style>

    <script>
      // helpful for understanding dojoConfig.packages vs. dojoConfig.paths:
      // http://www.sitepen.com/blog/2013/06/20/dojo-faq-what-is-the-difference-packages-vs-paths-vs-aliases/
      var dojoConfig = { 
        paths: {
          extras: location.pathname.replace(/\/[^/]+$/, "") + "/extras"
        }
      };
    </script>
    <script src="https://js.arcgis.com/3.20/"></script>
    <script>
      var map;
      require([
        "dojo/parser", 
        "dojo/ready",
        "dojo/_base/array",
        "esri/Color",
        "dojo/dom-style",
        "dojo/query",

        "esri/map", 
        "esri/request",
        "esri/graphic",
        "esri/geometry/Extent",

        "esri/symbols/SimpleMarkerSymbol",
        "esri/symbols/SimpleFillSymbol",
        "esri/symbols/PictureMarkerSymbol",
        "esri/renderers/ClassBreaksRenderer",

        "esri/layers/GraphicsLayer",
        "esri/SpatialReference",
        "esri/dijit/PopupTemplate",
        "esri/geometry/Point",
        "esri/geometry/webMercatorUtils",

        "extras/ClusterLayer",

        "dijit/layout/BorderContainer", 
        "dijit/layout/ContentPane", 
        "dojo/domReady!"
      ], function(
        parser, ready, arrayUtils, Color, domStyle, query,
        Map, esriRequest, Graphic, Extent,
        SimpleMarkerSymbol, SimpleFillSymbol, PictureMarkerSymbol, ClassBreaksRenderer,
        GraphicsLayer, SpatialReference, PopupTemplate, Point, webMercatorUtils,
        ClusterLayer
      ) {
        ready(function() {
          parser.parse();

          var clusterLayer;
          var popupOptions = {
            "markerSymbol": new SimpleMarkerSymbol("circle", 20, null, new Color([0, 0, 0, 0.25])),
            "marginLeft": "20",
            "marginTop": "20"
          };
          map = new Map("map", {
            basemap: "oceans",
            center: [-117.789, 33.543],
            zoom: 13
          });

          map.on("load", function() {
            // hide the popup's ZoomTo link as it doesn't make sense for cluster features
            domStyle.set(query("a.action.zoomTo")[0], "display", "none");

            // get the latest 1000 photos from instagram/laguna beach
            var photos = esriRequest({
              url: "data/1000-photos.json",
              handleAs: "json"
            });
            photos.then(addClusters, error);
          });

          function addClusters(resp) {
            var photoInfo = {};
            var wgs = new SpatialReference({
              "wkid": 4326
            });
            photoInfo.data = arrayUtils.map(resp, function(p) {
              var latlng = new  Point(parseFloat(p.lng), parseFloat(p.lat), wgs);
              var webMercator = webMercatorUtils.geographicToWebMercator(latlng);
              var attributes = {
                "Caption": p.caption,
                "Name": p.full_name,
                "Image": p.image,
                "Link": p.link
              };
              return {
                "x": webMercator.x,
                "y": webMercator.y,
                "attributes": attributes
              };
            });

            // popupTemplate to work with attributes specific to this dataset
            var popupTemplate = new PopupTemplate({
              "title": "",
              "fieldInfos": [{
                "fieldName": "Caption",
                visible: true
              }, {
                "fieldName": "Name",
                "label": "By",
                visible: true
              }, {
                "fieldName": "Link",
                "label": "On Instagram",
                visible: true
              }]
            });

            // cluster layer that uses OpenLayers style clustering
            clusterLayer = new ClusterLayer({
              "data": photoInfo.data,
              "distance": 100,
              "id": "clusters",
              "labelColor": "#fff",
              "labelOffset": 10,
              "resolution": map.extent.getWidth() / map.width,
              "singleColor": "#888",
              "singleTemplate": popupTemplate
            });
            var defaultSym = new SimpleMarkerSymbol().setSize(4);
            var renderer = new ClassBreaksRenderer(defaultSym, "clusterCount");

            var picBaseUrl = "https://static.arcgis.com/images/Symbols/Shapes/";
            var blue = new PictureMarkerSymbol(picBaseUrl + "BluePin1LargeB.png", 32, 32).setOffset(0, 15);
            var green = new PictureMarkerSymbol(picBaseUrl + "GreenPin1LargeB.png", 64, 64).setOffset(0, 15);
            var red = new PictureMarkerSymbol(picBaseUrl + "RedPin1LargeB.png", 72, 72).setOffset(0, 15);
            renderer.addBreak(0, 2, blue);
            renderer.addBreak(2, 200, green);
            renderer.addBreak(200, 1001, red);

            clusterLayer.setRenderer(renderer);
            map.addLayer(clusterLayer);

            // close the info window when the map is clicked
            map.on("click", cleanUp);
            // close the info window when esc is pressed
            map.on("key-down", function(e) {
              if (e.keyCode === 27) {
                cleanUp();
              }
            });
          }

          function cleanUp() {
            map.infoWindow.hide();
            clusterLayer.clearSingles();
          }

          function error(err) {
            console.log("something failed: ", err);
          }

          // show cluster extents...
          // never called directly but useful from the console 
          window.showExtents = function() {
            var extents = map.getLayer("clusterExtents");
            if ( extents ) {
              map.removeLayer(extents);
            }
            extents = new GraphicsLayer({ id: "clusterExtents" });
            var sym = new SimpleFillSymbol().setColor(new Color([205, 193, 197, 0.5]));

            arrayUtils.forEach(clusterLayer._clusters, function(c, idx) {
              var e = c.attributes.extent;
              extents.add(new Graphic(new Extent(e[0], e[1], e[2], e[3], map.spatialReference), sym));
            }, this);
            map.addLayer(extents, 0);
          };
        });
      });
    </script>
  </head>
  
  <body>
    <div data-dojo-type="dijit/layout/BorderContainer" 
         data-dojo-props="design:'headline',gutters:false" 
         style="width: 100%; height: 100%; margin: 0;">
      <div id="map" 
           data-dojo-type="dijit/layout/ContentPane" 
           data-dojo-props="region:'center'"> 
      </div>
    </div>
  </body>
</html>