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!
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; }); } } }); }); }); }
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.
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.
Joel,
Thank you for this code. I will figure out how to implement it and get back to you with my results.
This worked perfectly. I really appreciate your help on this! Thank you!
p.s. is there anyway to make the legend display in ascending order? The values are randomly in order.
Once again you have solved the answer perfectly. Thank you again.
There is a possibility of an infinite loop occurring since setting the renderer will result in the "updating" property being set, and when the "updating" property is set to false, the process starts over again. However, that's what the "ignoreRendererSet" variable exists to prevent, and I don't see any immediate problem with it not doing its job. I would recommend using the browser's developer tools to add some breakpoints and step through the code to help determine where the infinite loop is occurring.
the code loop seems to be around this line:
infos.forEach (function (uniqueValueInfo)
It will work for a few buildings then will start to loop.
I took a closer look at your post from Monday, and think I see the problem. Here is what you posted reproduced for convenience:
function zoomRoomUse (ids) { require ([ "esri/rest/query", "esri/rest/support/Query", "esri/layers/FeatureLayer", "esri/views/layers/LayerView" ], function (query, Query, FeatureLayer, LayerView) { roomsUseLayer.definitionExpression = roomsUseLayer.objectIdField + " IN(" + ids.join (",") + ")"; // console.log (roomsUseLayer.definitionExpression) view.whenLayerView (roomsUseLayer).then (function (layerView) { var query = roomsUseLayer.createQuery (); query.objectIds = [ids]; query.where = roomsUseLayer.definitionExpression; roomsUseLayer.queryFeatures (query).then (function (results) { const features = results.features; var geometry = features[0].geometry.extent.clone (); for (var k = 0; k < features.length; k++) { geometry = geometry.union (features[k].geometry.extent.clone ()); view.goTo (geometry).then (function () { roomsUseLayer.visible = true; manageDynamicSymbology (roomsUseLayer, view); }).catch (function (error) { if (error.name != "view:goto-interrupted") { console.error (error); } else { console.log ("Drawing"); } }); } }); }); }); }
I think the main problem is that you're calling "manageDynamicSymbology" inside of a loop on line 27. The function manageDynamicSymbology should only ever be called one time, preferably when the layer is created. It never needs to be called again afterwards. For example:
var layer = new FeatureLayer({ url: "https://my/url/MapServer/0", id: "myLayerID" }); manageDynamicSymbology(layer, view); view.map.add(layer);
Another problem is that you're also calling view.goTo inside the loop, but you want the loop to finish before calling that function. This way, you would only zoom one time to the full extent of the graphics. Here is the updated code:
function zoomRoomUse(ids) { require ([ "esri/rest/query", "esri/rest/support/Query", "esri/layers/FeatureLayer", "esri/views/layers/LayerView" ], function (query, Query, FeatureLayer, LayerView) { roomsUseLayer.definitionExpression = roomsUseLayer.objectIdField + " IN(" + ids.join (",") + ")"; // console.log (roomsUseLayer.definitionExpression) view.whenLayerView (roomsUseLayer).then (function (layerView) { var query = roomsUseLayer.createQuery (); query.objectIds = [ids]; query.where = roomsUseLayer.definitionExpression; roomsUseLayer.queryFeatures (query).then (function (results) { const features = results.features; var geometry = features[0].geometry.extent.clone(); for (var k = 1; k < features.length; k++) { geometry = geometry.union (features[k].geometry.extent.clone()); } view.goTo (geometry).then (function () { roomsUseLayer.visible = true; }).catch (function (error) { if (error.name != "view:goto-interrupted") { console.error (error); } else { console.log ("Drawing"); } }); }); }); }); }
This is getting better! The only thing that isnt working is the intial zoom to still displays the whole legend not the dynamic part. If i pan or do an identify then the legend refreshes to the dynamic legend.
If i use roomsUseLayer.refresh() in the console that updates the legend.
Ok, I see the problem. In the "manageDynamicSymbology" function, you'll want to change this:
if (featureSet.features.length === 0) featureLayer.renderer = originalRenderer; else {
to this:
if (featureSet.features.length === 0) { if (featureLayer.renderer != originalRenderer) featureLayer.renderer = originalRenderer; else ignoreRendererSet = false; } else {
Přihlášení členové mohou přispívat, sledovat aktualizace a další. Jste tu noví? Zaregistrujte si bezplatný účet.
Find useful guides, FAQs, and documents to help you navigate and make the most of Esri Community.