Select to view content in your preferred language

Javascript 4.x legend refresh

1900
24
Jump to solution
11-15-2024 02:51 PM
Mr_Kirkwood
Regular Contributor

I am using JS 4.30. I would like ot figure out how to get the legend to update the symbology of what is in the map when zoomed in. The legend for one of my layers has over 200 symbols. I have a fucntion to zoom into a specific building and floor. That building and floor does not have all 200 symbols it only has 10. I would like the legedn to update and show only the 10 symbols in the map.

The print funtion does this see attached. 

I appreciate any help Thank you! 

Tags (3)
0 Kudos
3 Solutions

Accepted Solutions
JoelBennett
MVP Regular Contributor

You could use something like this, which appears to do the trick.  Basically, after every time the layer redraws, a new renderer is created with only the symbology visible in the view, and then assigned to the layer.  Just pass in references to your featureLayer and view and it does the rest:

function manageDynamicSymbology(featureLayer, view) {
	featureLayer.when(function() {
		view.whenLayerView(featureLayer).then(function(layerView) {
			var originalRenderer = featureLayer.renderer;
			var ignoreRendererSet = false;

			layerView.watch("updating", function(newValue, oldValue, propertyName, target) {
				if ((!newValue) && (oldValue)) {
				  if (ignoreRendererSet)
				    ignoreRendererSet = false;
				  else {
				    ignoreRendererSet = true;

				    var query = layerView.createQuery();
				    query.geometry = view.extent;
  
  					layerView.queryFeatures(query).then(function(featureSet) {
  						if (featureSet.features.length === 0)
  							featureLayer.renderer = originalRenderer;
  						else {
  							var promises = [];
  
  							featureSet.features.forEach(function(feature) {
  								promises.push(originalRenderer.getUniqueValueInfo(feature));
  							});
  
  							Promise.all(promises).then(function(infos) {
  								var uniqueValueInfos = [];
  								var defaultSymbol = null;
  								var defaultLabel = null;
  
  								infos.forEach(function(uniqueValueInfo) {
  								    if (!uniqueValueInfo) {
  								      if (defaultSymbol === null) {
  								        defaultSymbol = originalRenderer.defaultSymbol.clone();
  								        defaultLabel = originalRenderer.defaultLabel;
  								      }
  								    } else if (!uniqueValueInfos.includes(uniqueValueInfo))
  								      uniqueValueInfos.push(uniqueValueInfo);
  								});

  							  var newRenderer = featureLayer.renderer.clone();
  							  newRenderer.uniqueValueInfos = uniqueValueInfos;
  							  newRenderer.defaultSymbol = defaultSymbol;
  							  newRenderer.defaultLabel = defaultLabel;

  							  featureLayer.renderer = newRenderer;
  							});
  						}
  					}, function(e) {
  						featureLayer.renderer = originalRenderer;
  					});
				  }
				}
			});
		});
	});
}

 

View solution in original post

JoelBennett
MVP Regular Contributor

I believe that if you replace line 43 with this, it will maintain their original order:

 

newRenderer.uniqueValueInfos = uniqueValueInfos.sort(function(a, b) {return originalRenderer.uniqueValueInfos.indexOf(a) - originalRenderer.uniqueValueInfos.indexOf(b);});

 

Otherwise, you can implement the sort function according to how you want.

View solution in original post

JoelBennett
MVP Regular Contributor

I see...yes, this approach will not work with MODE_SELECTION since the graphics array won't be populated unless something is selected, and even then, only the selected features will be present in the graphics array.

Based on the info provided, a suitable workaround might be to use a MODE_ONDEMAND layer, with the definition expression set to only show rooms for the current floor.  See also getDefinitionExpression and setDefinitionExpression.

View solution in original post

0 Kudos
24 Replies
JoelBennett
MVP Regular Contributor

You could use something like this, which appears to do the trick.  Basically, after every time the layer redraws, a new renderer is created with only the symbology visible in the view, and then assigned to the layer.  Just pass in references to your featureLayer and view and it does the rest:

function manageDynamicSymbology(featureLayer, view) {
	featureLayer.when(function() {
		view.whenLayerView(featureLayer).then(function(layerView) {
			var originalRenderer = featureLayer.renderer;
			var ignoreRendererSet = false;

			layerView.watch("updating", function(newValue, oldValue, propertyName, target) {
				if ((!newValue) && (oldValue)) {
				  if (ignoreRendererSet)
				    ignoreRendererSet = false;
				  else {
				    ignoreRendererSet = true;

				    var query = layerView.createQuery();
				    query.geometry = view.extent;
  
  					layerView.queryFeatures(query).then(function(featureSet) {
  						if (featureSet.features.length === 0)
  							featureLayer.renderer = originalRenderer;
  						else {
  							var promises = [];
  
  							featureSet.features.forEach(function(feature) {
  								promises.push(originalRenderer.getUniqueValueInfo(feature));
  							});
  
  							Promise.all(promises).then(function(infos) {
  								var uniqueValueInfos = [];
  								var defaultSymbol = null;
  								var defaultLabel = null;
  
  								infos.forEach(function(uniqueValueInfo) {
  								    if (!uniqueValueInfo) {
  								      if (defaultSymbol === null) {
  								        defaultSymbol = originalRenderer.defaultSymbol.clone();
  								        defaultLabel = originalRenderer.defaultLabel;
  								      }
  								    } else if (!uniqueValueInfos.includes(uniqueValueInfo))
  								      uniqueValueInfos.push(uniqueValueInfo);
  								});

  							  var newRenderer = featureLayer.renderer.clone();
  							  newRenderer.uniqueValueInfos = uniqueValueInfos;
  							  newRenderer.defaultSymbol = defaultSymbol;
  							  newRenderer.defaultLabel = defaultLabel;

  							  featureLayer.renderer = newRenderer;
  							});
  						}
  					}, function(e) {
  						featureLayer.renderer = originalRenderer;
  					});
				  }
				}
			});
		});
	});
}

 

Mr_Kirkwood
Regular Contributor

Joel,

Thank you for this code. I will figure out how to implement it and get back to you with my results. 

0 Kudos
Mr_Kirkwood
Regular Contributor

Joel,

This worked perfectly. I really appreciate your help on this! Thank you!

0 Kudos
Mr_Kirkwood
Regular Contributor

p.s. is there anyway to make the legend display in ascending order?  The values are randomly in order. 

0 Kudos
JoelBennett
MVP Regular Contributor

I believe that if you replace line 43 with this, it will maintain their original order:

 

newRenderer.uniqueValueInfos = uniqueValueInfos.sort(function(a, b) {return originalRenderer.uniqueValueInfos.indexOf(a) - originalRenderer.uniqueValueInfos.indexOf(b);});

 

Otherwise, you can implement the sort function according to how you want.

Mr_Kirkwood
Regular Contributor

Once again you have solved the answer perfectly. Thank you again. 

0 Kudos
Mr_Kirkwood
Regular Contributor

Joel,

Is there a way to achieve this for JS 3.x? I still have some legacy apps that I would like to maintain until I get the JS 4.x apps up and running. 

 

Thanks again for all of your help! 

0 Kudos
JoelBennett
MVP Regular Contributor

This seems to do it:

function manageDynamicSymbology(featureLayer, map) {
	if (featureLayer.loaded)
		manageDynamicSymbology2(featureLayer, map);
	else {
		featureLayer.on("load", function() {
			manageDynamicSymbology2(featureLayer, map);
		});
	}
}

function manageDynamicSymbology2(featureLayer, map) {
	require(["esri/renderers/jsonUtils", "esri/symbols/jsonUtils"], function(rendererJsonUtils, symbolJsonUtils) {
		var originalRenderer = featureLayer.renderer;

		featureLayer.on("update-end", function() {
			var infos = [];

			featureLayer.graphics.forEach(function(graphic) {
				if (map.extent.intersects(graphic.geometry)) {
					var info = originalRenderer.getUniqueValueInfo(graphic);

					if (info)
						infos.push(info);
				}
			});

			var uniqueValueInfos = [];
			var defaultSymbol = null;
  			var defaultLabel = null;

			infos.forEach(function(uniqueValueInfo) {
				if (!uniqueValueInfo) {
					if ((defaultSymbol === null) && (originalRenderer.defaultSymbol)) {
						defaultSymbol = symbolJsonUtils.fromJson(originalRenderer.defaultSymbol.toJson());
						defaultLabel = originalRenderer.defaultLabel;
					}
				} else if (!uniqueValueInfos.includes(uniqueValueInfo))
					uniqueValueInfos.push(uniqueValueInfo);
			});

			var newRenderer = rendererJsonUtils.fromJson(featureLayer.renderer.toJson());
			newRenderer.infos = uniqueValueInfos.sort(function(a, b) {return originalRenderer.infos.indexOf(a) - originalRenderer.infos.indexOf(b);});
			newRenderer.defaultSymbol = defaultSymbol;
			newRenderer.defaultLabel = defaultLabel;

			featureLayer.setRenderer(newRenderer);
		});
	});
}
0 Kudos
Mr_Kirkwood
Regular Contributor

That works to a degree. For my zoomTo function, I have to set the Feature layer to these settings:

     roomUseLayer = new FeatureLayer (urlRoomsUse, {
                    mode: FeatureLayer.MODE_SELECTION,
                    id: 'roomUseLayer',
                    visible: true,
                    outFields: ["*"],
                    opacity: 0.6,
                })

Using FeatureLayer.MODE_SELECTION does not update the legend. If i use  FeatureLayer.MODE_ONDEMAND the legend updates but all the floors draw not just the one being  zoomed into.  Not sure what the best approach is for this? 

 

 

0 Kudos