Hello all,
I am hoping to get some clarification around properties and structures of items for this scenario:
I have a Web Map containing a Point Layer, with the symbology based on a field in that layer ("Stop_Number"). By default, the label in the Legend is just the value of the field that determines the symbology, i.e. Stop_Number - so in this case, stops are labeled things like 1, 2, 3, etc.
I want to programmatically change the labels in the Legend to pull values from another field, "Title". From what I have found so far, it should be possible to do this by modifying the renderer portion JSON layer definition using the Python API:
Does this approach seem correct? Do I have my properties and structures figured out correctly? If so, I would deeply appreciate any suggestions for the syntax of making the actual changes - currently my conceptual framework is to iterate through 'values' in the 'classes' of 'uniqueValueGroups' 'headings', then set the value for 'label' to be the dict value whose key corresponds the current 'values' value? How do I implement/save the changes to the JSON item once made?
Apologies for any unclear language used here - I am learning my way around the API and the JSON items. Thanks in advance for any suggestions!
Solved! Go to Solution.
Hi @JasperRomero,
Your approach is correct.
First take a look at the makeup of the renderer to get familiar.
from arcgis import GIS
import json
## Access ArcGIS Online
agol = GIS("home")
## get the WebMap as an Item object
wm_item = agol.content.get("WM_ITEM_ID")
## get the WebMap definition
wm_item_data = wm_item.get_data()
## the name of the layer you want to update
layer_name = "LAYER_NAME"
## grab the renderer for that layer
renderer = [
lyr["layerDefinition"]["drawingInfo"]["renderer"]
for lyr in wm_item_data["operationalLayers"]
if lyr["title"] == layer_name
][0]
## print the renderer to screen
print(json.dumps(renderer, indent=4))
You will want to create a match table for "Stop_Number" to "Title". I recommend a dictionary. You will access the WebMap JSON Definition as a dictionary returned from get_data(). Manipulate this dictionary and then reapply it as the WebMap item definition. See below. Code is comment, and I hope it is easy enough to follow.
from arcgis import GIS
## Access ArcGIS Online
agol = GIS("home")
## get the WebMap as an Item object
wm_item = agol.content.get("WM_ITEM_ID")
## get the WebMap definition - we will update this
wm_item_data = wm_item.get_data()
## the name of the layer to update
layer_name = "LAYER_NAME"
## the match dictionary (you can generate dynamically from the data)
legend_dict = {
1 : "Stop 1", # "1" if your value is a text
2 : "Stop 2",
3 : "Stop 3",
4 : "Stop 4"
}
## for each layer on the definition operationalLayers
for layer in wm_item_data["operationalLayers"]:
## we are on,ly interested in this layer
if layer["title"] == layer_name:
## access the information to update
for entry in layer["layerDefinition"]["drawingInfo"]["renderer"]["uniqueValueGroups"][0]["classes"]:
## if there is a label entry in the ductionary
if entry["label"] in legend_dict:
## update the label based on the match in the dictionary
entry["label"] = legend_dict[entry["label"]]
## update the WebMap item definition
wm_item.update({"text" : wm_item_data})
All the best,
Glen
Hi @JasperRomero,
Your approach is correct.
First take a look at the makeup of the renderer to get familiar.
from arcgis import GIS
import json
## Access ArcGIS Online
agol = GIS("home")
## get the WebMap as an Item object
wm_item = agol.content.get("WM_ITEM_ID")
## get the WebMap definition
wm_item_data = wm_item.get_data()
## the name of the layer you want to update
layer_name = "LAYER_NAME"
## grab the renderer for that layer
renderer = [
lyr["layerDefinition"]["drawingInfo"]["renderer"]
for lyr in wm_item_data["operationalLayers"]
if lyr["title"] == layer_name
][0]
## print the renderer to screen
print(json.dumps(renderer, indent=4))
You will want to create a match table for "Stop_Number" to "Title". I recommend a dictionary. You will access the WebMap JSON Definition as a dictionary returned from get_data(). Manipulate this dictionary and then reapply it as the WebMap item definition. See below. Code is comment, and I hope it is easy enough to follow.
from arcgis import GIS
## Access ArcGIS Online
agol = GIS("home")
## get the WebMap as an Item object
wm_item = agol.content.get("WM_ITEM_ID")
## get the WebMap definition - we will update this
wm_item_data = wm_item.get_data()
## the name of the layer to update
layer_name = "LAYER_NAME"
## the match dictionary (you can generate dynamically from the data)
legend_dict = {
1 : "Stop 1", # "1" if your value is a text
2 : "Stop 2",
3 : "Stop 3",
4 : "Stop 4"
}
## for each layer on the definition operationalLayers
for layer in wm_item_data["operationalLayers"]:
## we are on,ly interested in this layer
if layer["title"] == layer_name:
## access the information to update
for entry in layer["layerDefinition"]["drawingInfo"]["renderer"]["uniqueValueGroups"][0]["classes"]:
## if there is a label entry in the ductionary
if entry["label"] in legend_dict:
## update the label based on the match in the dictionary
entry["label"] = legend_dict[entry["label"]]
## update the WebMap item definition
wm_item.update({"text" : wm_item_data})
All the best,
Glen
Thank you @Clubdebambos ! This is so helpful, I was 90% there but missing a few pieces of syntax.
Out of curiosity - is there any way to apply this remap dynamically, i.e. without having to rerun the script any time changes are made? Not a real issue if not - some people using the workflow this is for are unfamiliar with using script tools, but if this is the best way to do it we will figure out how to address that.
Thank you again!
Hi @JasperRomero,
I don't know of a way to do this dynamically. My question would be, why can't you use the Title field to create the symbology you are making? Is there a reason why you use Stop_Number and then want to update to a Title?
@Clubdebambos fair point - this is for a workflow to make a series of public-facing web apps, where each app is a map for a different walking trail. All the data lives within a single layer, and each different map/trail filters by Trail Name. The Stop_Number is needed to set the POI icons, but the folks I am making this for want the legend to show the Stop_Number for the Trail with the Title of that stop. So within the overall layer, there are repeated Stop_Numbers (i.e. each trail has a Stop 1, Stop 2, etc.) but each Stop_Number has a unique Title on a per Trail_Name basis. Does this make sense?
I suppose I will probably want to adjust the code slightly to reflect this - something like adding a conditional before the remapping dictionary for the legend, to select only Stop_Numbers and Titles whose Trail_Name matches the Trail Name of the Web Map. Would this be the correct approach?
Thank you again for your time - sincerely appreciate the help!
Hi @JasperRomero,
You can use an Arcade expression.
Use something like below.
return $feature.Stop_Number + ": " + $feature.Title
You could set this on the Feature Layer so every time it is added to a Map the legend is already configured.
All the best,
Glen
Hi @Clubdebambos ,
That might end up being the easiest solution - I currently have an Arcade Expression configured for symbology to assign the appropriate symbol based on whether a feature is a main stop (i.e. has a Stop_Number) or is an additional feature (restrooms etc.). Lots of little moving pieces - I didn't want to add excess information in the original post that would muddy the waters, but I guess this info is relevant to how best to solve the problem. I suppose as long as the folks requesting these maps don't mind the "#: Title" format (as opposed to just "Title", with symbol based on number) Arcade is probably the most scalable fix for the least effort.
Thanks for all your suggestions - I really appreciate the input and consistent responses!