Is it possible to configure a legend to only include features which are visible?

281
3
01-30-2024 04:31 AM
EmilyGoldsmith
New Contributor III

Hello.

In my spatial query app, i've configured the legend to dynamically show only those layers which are present within a mapview. However, I was wondering if it was possible to go one step further and show only those features which have been filtered to the view?

For example, even though this point has no features intersecting with it (and thus filtered), a layer shows up in the legend due to being present in the mapview:

EmilyGoldsmith_0-1706612202493.png

EmilyGoldsmith_1-1706612262715.png

My legend is configured like this:

        const legend = new Legend({
            view: view
          });  
        legend.hideLayersNotInCurrentView = true; 

        //Add expandable functionality to the legend
        legendExpand = new Expand({
          expandIcon: "layers",  
          view: view,
          content: legend,
          expanded: true
});

Thank you. 

Tags (2)
0 Kudos
3 Replies
JoelBennett
MVP Regular Contributor

Although this is not possible with the out of the box Legend widget, it is still possible for you to accomplish this behavior in your application, assuming the layers you're working with are client-side layers (e.g. FeatureLayers).  This post shows how to use multiple Legend widgets, although having the appearance of being a single legend to the end user.  If you're using FeatureLayers, you could add one Legend per layer.  For each layer, you would get a reference to its associated FeatureLayerView, and then whenever it refreshes it, you would query the FeatureLayerView for its features.  This query runs client-side, so is instantaneous.  If it returns no features, you would hide the associated Legend instance, or if there are features returned, you would show it instead.

EmilyGoldsmith
New Contributor III

Hello, many thanks for this.

I had a go with configuring it so that the featurelayer is queried on refresh and the geometry the user draws is used to query it, but i'm definitely sure i've gone wrong somewhere.

 Could you provide some critiques if possible.

        //Loop through webmap layers to retrieve features
        map.layers.forEach((FeatureLayer) => { //For each layer in the webmap 
          FeatureLayer.on("refresh", function(event){ //On refresh
              if (event.dataChanged){ //If a change has happened
               const queryParams = FeatureLayer.createQuery(); //Create a feature query
               queryParams.geometry = sketchGeometry; //Set the feature query geometry as the one drawn by the user
               queryParams.returnGeometry = true;
                FeatureLayer.queryFeatures(queryParams).then(function(results){ //returns all the graphics from the layer view
                  map.layers.add(results.features);    }) }})});
                               
        view.when(() => {//At the view
         var container = document.createElement("DIV"); //Create a container for the layer's legend
          container.style.backgroundColor = "#FFFFFF"; //Set container's background colour
          view.ui.add(container, "bottom-left"); // Add widget to the bottom right corner of the view
         addLegendForLayer(Legend, view, container, map.layers.getItemAt(0));//Run the addLegendForLayer() custom function to add the legend into the container             
        });

 

0 Kudos
JoelBennett
MVP Regular Contributor

My apologies, I sent you in the wrong direction due to a misunderstanding of the refresh event.  I thought it would fire every time the layer refreshed itself, but that's not the case.  Instead, the trigger we're looking for is the associated FeatureLayerView's updating property; when it changes from true to false, the layer has just finished refreshing.  It is then that we should query the FeatureLayerView to see if it has any features, in which case I've also found it's best to use the view's extent (simply getting the count of the FeatureLayerView's available features can include features that aren't visible on screen).

I've modified the code for the sample mentioned in the other thread, and is reproduced in its fullness below:

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
    <title>Legend widget | Sample | ArcGIS Maps SDK for JavaScript 4.28</title>
    <link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/themes/light/main.css" />

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

    <script src="https://js.arcgis.com/4.28/"></script>
    <script>
      function addLegendForLayer(Legend, view, container, layer) {
        var childElement = document.createElement("DIV");
        container.appendChild(childElement);

        const legend = new Legend({
          view: view,
          layerInfos: [
            {
              layer: layer,
              title: "NY Educational Attainment"
            }
          ],
          container: childElement
        });

        return {legend:legend,layer:layer};
      }

      require(["esri/views/MapView", "esri/widgets/Legend", "esri/WebMap"], (
        MapView,
        Legend,
        WebMap
      ) => {
        const webmap = new WebMap({
          portalItem: {
            // autocasts as new PortalItem()
            id: "05e015c5f0314db9a487a9b46cb37eca"
          }
        });

        const view = new MapView({
          container: "viewDiv",
          map: webmap
        });

        view.when(() => {
          var container = document.createElement("DIV");
          container.style.backgroundColor = "#FFFFFF";

          // Add widget to the bottom right corner of the view
          view.ui.add(container, "bottom-right");
          
          addLegendForLayer(Legend, view, container, webmap.layers.getItemAt(0));
          var item = addLegendForLayer(Legend, view, container, webmap.layers.getItemAt(0));

          var legend2 = item.legend;
          var layer2 = item.layer;

          view.whenLayerView(layer2).then(function(layerView2) {
            layerView2.watch("updating", function(newValue, oldValue, propertyName, target) {
              if ((oldValue) && (!newValue)) {
                var query = layerView2.createQuery();
                query.geometry = view.extent;
                
                layerView2.queryFeatures(query).then(function(featureSet) {
                  legend2.visible = (featureSet.features.length > 0);
                });
              }
            });
          });
        });
      });
    </script>
  </head>

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

 

To view this in action, go to the sample's sandbox, replace the code in the pane on the left with what's above (you can copy and paste), and click the "Refresh" button near the top-right of the page.  After it's loaded, zoom to an area where there are no features, and the 2nd legend item disappears.  Zoom back out to where there are features, and the legend reappears.

0 Kudos