Get underlying object id graphics in cluster + programmatically display feature reduction pop up template with custom content

1723
1
Jump to solution
12-04-2020 07:27 AM
by Anonymous User
Not applicable

Hi,

My requirements: 

1. Get the underlying features in object ids from a cluster graphic. E.g. if cluster A have 5 features, get all 5 object ids when user clicks on it. 

2. Pass that object ids as parameters into a custom content widget that will display fields relating to those 5 features in a pop up. The custom content widget will serves as the content for a new pop up template. 

3. Display the pop up template when the user clicks on a cluster. 

Conclusion, when user clicks on cluster A with 5 features inside the cluster, a pop up opens that displays field X,Y,Z for each features. 

Solution thus far :

1. Create a graphics layer and add to view 

2. Create a listener for 'clicks' on the view

3. Pass the event of that listener to a hitTest that includes the feature layer 

4. Get the graphic of the cluster 

5. Create a geodesicBuffer based on the graphic geometry with an approximate size. [Question!!! Is there a way to match the size of the cluster graphics with the input options for geodesicBuffer? ]

6. Add a new graphic to the graphics layer with geometry as the geodesicBuffer

7. Query object ids on the feature layer by passing the geometry of the first graphic in the graphics layer, and defining a spatial relationship of intersect

8. The result should return an array of object ids 

9. Create a new CustomContent class and pass the object ids as an argument into creator method, to display the array of object ids in a div element

10. Create a new PopupTemplate class and pass the custom content created at #9 as content 

11. Remove all graphics from the graphics layer

12. Set the featurelayer.featureReduction.popupTemplate to be equal to the newly created pop up template at #10. 

Problems:

1. Because the featurelayer.featureReduction.popupTemplate is only after the click is pressed, and pop up templates are to display be default with a click. What I observe is that the pop up will initially display the previous object ids. Once the user clicks on the same cluster again, the featurelayer.featureReduction.popupTemplate is updated, and now the user will see the correct object ids in the pop up. 

 

For example, user clicks on cluster A with object ids [1,2,3], the pop up displays [1,2,3]. When user clicks on cluster B with object ids [4,5,6], the pop up displays [1,2,3], when the user clicks on cluster B again, the pop up displays [4,5,6] 

Question!! : Is there a way to override the default option of the pop up displaying? Much like programmatically turning the pop up on a view.. I do not see an equivalent for pop up templates.

2. Is there a way to create a graphic layers that is a copy of the clustered feature layer? Being able to do so would mean that I can create the graphics that reflects the cluster graphic in advance. 

 

I have modified a sample to reflect my problem. 

Thanks in advance. 

 

 

<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />

    <!--
  ArcGIS API for JavaScript, https://js.arcgis.com
  For more information about the featurereduction-cluster-filter sample, read the original sample description at developers.arcgis.com.
  https://developers.arcgis.com/javascript/latest/sample-code/featurereduction-cluster-filter/index.html
  -->
    <title>
      Point clustering - generate suggested configuration | Sample | ArcGIS API
      for JavaScript 4.17
    </title>

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

    <style>
      html,
      body,
      #viewDiv {
        height: 100%;
        width: 100%;
        margin: 0;
        padding: 0;
      }
      #infoDiv {
        background: white;
        padding: 10px;
      }
    </style>

    <script>
      require([
        "esri/WebMap",
        "esri/views/MapView",
        "esri/layers/FeatureLayer",
        "esri/widgets/Legend",
        "esri/widgets/Expand",
        "esri/smartMapping/labels/clusters",
        "esri/smartMapping/popup/clusters",
        "esri/core/promiseUtils",
        "esri/geometry/geometryEngine",
        "esri/layers/GraphicsLayer",
        "esri/Graphic",
        "esri/PopupTemplate",
        "esri/popup/content/CustomContent",
      ], function (
        Map,
        MapView,
        FeatureLayer,
        Legend,
        Expand,
        clusterLabelCreator,
        clusterPopupCreator,
        promiseUtils,
        geometryEngine,
        GraphicsLayer,
        Graphic,
        PopupTemplate,
        CustomContent
      ) {
        const serviceUrl =
          "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/Places_of_Worship_India/FeatureServer/0";
        const layer = new FeatureLayer({
          url: serviceUrl,
          title: "Places of worship",
          outFields: ["name", "religion", "denomination"],
          popupTemplate: {
            title: "{name}",
            content: [
              {
                type: "fields",
                fieldInfos: [
                  {
                    fieldName: "religion",
                  },
                  {
                    fieldName: "denomination",
                  },
                ],
              },
            ],
          },
        });

        const map = new Map({
          basemap: "gray-vector",
          layers: [layer],
        });

        const view = new MapView({
          container: "viewDiv",
          map: map,
          center: [80.20127, 22.12355],
          zoom: 4,
        });

        const legend = new Legend({
          view: view,
          container: "legendDiv",
        });

        const infoDiv = document.getElementById("infoDiv");
        view.ui.add(
          new Expand({
            view: view,
            content: infoDiv,
            expandIconClass: "esri-icon-layer-list",
            expanded: true,
          }),
          "top-right"
        );

        view.on("click", function () {
          layer
            .when()
            .then(generateClusterConfig)
            .then(function (featureReduction) {
              layer.featureReduction = featureReduction;

              console.log("layer", layer);

              const toggleButton = document.getElementById("toggle-cluster");
              toggleButton.addEventListener("click", toggleClustering);

              // To turn off clustering on a layer, set the
              // featureReduction property to null
              function toggleClustering() {
                if (isWithinScaleThreshold()) {
                  let fr = layer.featureReduction;
                  layer.featureReduction =
                    fr && fr.type === "cluster" ? null : featureReduction;
                }
                toggleButton.innerText =
                  toggleButton.innerText === "Enable Clustering"
                    ? "Disable Clustering"
                    : "Enable Clustering";
              }

              view.whenLayerView(layer).then(function (layerView) {
                const filterSelect = document.getElementById("filter");
                // filters the layer using a definitionExpression
                // based on a religion selected by the user
                filterSelect.addEventListener("change", function (event) {
                  const newValue = event.target.value;

                  const whereClause = newValue
                    ? "religion = '" + newValue + "'"
                    : null;
                  layerView.filter = {
                    where: whereClause,
                  };
                  // close popup for former cluster that no longer displays
                  view.popup.close();
                });
              });

              view.watch("scale", function (scale) {
                if (toggleButton.innerText === "Disable Clustering") {
                  layer.featureReduction = isWithinScaleThreshold()
                    ? featureReduction
                    : null;
                }
              });
            })

            .catch(function (error) {
              console.error(error);
            });
        });

        function isWithinScaleThreshold() {
          return view.scale > 50000;
        }

        let bufferLayer = new GraphicsLayer();
        let customPopupTemplate;

        view.map.add(bufferLayer);

        function generateClusterConfig(layer) {
          view.on("click", function (e) {
            view.hitTest(e, { include: layer }).then(function (res) {
              //   console.log("res", res);

              if (res.results.length) {
                var graphic = res.results[0].graphic;

                console.log("graphic:", graphic);

                var buffer = geometryEngine.geodesicBuffer(
                  graphic.geometry,
                  50,
                  "kilometers"
                );
                bufferLayer.add(
                  new Graphic({
                    geometry: buffer,
                    symbol: {
                      type: "simple-fill", // autocasts as new SimpleFillSymbol()
                      color: "black",
                      outline: {
                        color: [0, 0, 0, 0.5],
                        width: 2,
                      },
                    },
                  })
                );

                // console.log("bufferLayer", bufferLayer);
                let popupEnabled;
                layer
                  .queryObjectIds({
                    geometry: bufferLayer.graphics.getItemAt(0).geometry,
                    spatialRelationship: "intersects",
                    returnGeometry: false,
                    outFields: ["*"],
                  })
                  .then(function (objectIds) {
                    // console.log(objectIds);
                    if (!objectIds.length) {
                      //   console.log("no object ids");
                      return;
                    } else {
                      const array = objectIds;

                      // This custom content returns a promise
                      let customContentWidget = new CustomContent({
                        outFields: ["*"],
                        creator: function (objectIds) {
                          const div = document.createElement("div");
                          //   console.log("array", array);
                          div.innerHTML = array.join(",");
                          return div;
                        },
                      });

                      customPopupTemplate = new PopupTemplate({
                        outFields: ["*"],
                        content: [customContentWidget],
                      });

                      bufferLayer.removeAll();

                      return {
                        type: "cluster",
                        popupTemplate: customPopupTemplate,
                        popupEnabled: popupEnabled,
                        labelingInfo: {
                          deconflictionStrategy: "none",

                          symbol: {
                            type: "text",
                            color: "#004a5d",
                            font: {
                              weight: "bold",
                              family: "Noto Sans",
                              size: 12,
                            },
                            xoffset: 0,
                            yoffset: -15,
                          },
                          labelPlacement: "center-center",
                          labelExpressionInfo: {
                            expression:
                              "Text($feature.cluster_count, '#,### plays')",
                          },
                        },
                        clusterMinSize: 20,
                      };
                    }
                  });

                // view.popup.open();
              }
            });
          });

          console.log("customPopupTemplate", customPopupTemplate);

          // generates default popupTemplate
          return {
            type: "cluster",
            popupTemplate: customPopupTemplate,
            labelingInfo: {
              deconflictionStrategy: "none",

              symbol: {
                type: "text",
                color: "#004a5d",
                font: {
                  weight: "bold",
                  family: "Noto Sans",
                  size: 12,
                },
                xoffset: 0,
                yoffset: -15,
              },
              labelPlacement: "center-center",
              labelExpressionInfo: {
                expression: "Text($feature.cluster_count, '#,### plays')",
              },
            },
            clusterMinSize: 20,
          };
        }
      });
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
    <div id="infoDiv" class="esri-widget">
      Filter by religion:
      <select id="filter" class="esri-select">
        <option value="">All</option>
        <option value="Hindu">Hindu</option>
        <option value="Christian">Christian</option>
        <option value="Muslim">Muslim</option>
        <option value="Buddhist">Buddhist</option>
        <option value="Sikh">Sikh</option>
        <option value="Jain">Jain</option>
      </select>
      <div style="padding-top: 10px">
        <button id="toggle-cluster" class="esri-button">
          Disable Clustering
        </button>
      </div>
      <div id="legendDiv"></div>
    </div>
  </body>
</html>

 

 

 

0 Kudos
1 Solution

Accepted Solutions
AnneFitz
Esri Regular Contributor

We just added support for querying features within a cluster at version 4.18 - check out this sample to see how it's done and for more information: https://developers.arcgis.com/javascript/latest/sample-code/featurereduction-cluster-query/index.htm...

 

View solution in original post

0 Kudos
1 Reply
AnneFitz
Esri Regular Contributor

We just added support for querying features within a cluster at version 4.18 - check out this sample to see how it's done and for more information: https://developers.arcgis.com/javascript/latest/sample-code/featurereduction-cluster-query/index.htm...

 

0 Kudos