I am trying to write code that symbolizes a feature in a UniqueValueRenderer that draws upon two fields: Status & tgt_ret. I want to have certain value combinations of these fields to by grouped into the same symbol/label. When intially 6 unique symbols are created for the 6 unique value combinations (Status, tgt_ret): [CLOSED, HE], [CLOSED, No Drop], [CLOSED, Practice], [OPEN, HE], [OPEN, No Drop], [OPEN Practice]
I only need three symbols formatted as follows:
[CLOSED, HE] with the label HE Closed
[[CLOSED, No Drop], [CLOSED, Practice]] with the label Closed
[[OPEN, HE], [OPEN, No Drop], [OPEN, Practice]] with the label Open
Does anyone have any idea on how to combine the values? I am having a hard time deciphering the the example code within the documentation for uniquevaluerenderer, Groups, and GroupItems. This is how I have it set up, but have not made much meaningful progress after this.
Target_layer= map_view.listLayers("Targets")[0]
arcpy.AddMessage("Target Layer Found")
Target_Sym= Target_layer.symbology
arcpy.AddMessage("Symbology Accessed")
Target_Sym.updateRenderer('UniqueValueRenderer')
arcpy.AddMessage("Update Renderer")
Target_Sym.renderer.fields= ['Status', 'tgt_ret']
arcpy.AddMessage("Renderer Fields Set")
Here some code, I haven't tested it but it should lead to right direction, I think.
import arcpy
# Get the target layer
Target_layer = map_view.listLayers("Targets")[0]
arcpy.AddMessage("Target Layer Found")
# Access the symbology
Target_Sym = Target_layer.symbology
arcpy.AddMessage("Symbology Accessed")
# Update the renderer to UniqueValueRenderer
Target_Sym.updateRenderer('UniqueValueRenderer')
arcpy.AddMessage("Update Renderer")
# Set the renderer fields
Target_Sym.renderer.fields = ['Status', 'tgt_ret']
arcpy.AddMessage("Renderer Fields Set")
# Clear existing values
Target_Sym.renderer.removeAllValues()
# Define the unique value combinations and their labels
value_combinations = {
("CLOSED", "HE"): "HE Closed",
("CLOSED", "No Drop"): "Closed",
("CLOSED", "Practice"): "Closed",
("OPEN", "HE"): "Open",
("OPEN", "No Drop"): "Open",
("OPEN", "Practice"): "Open"
}
# Add the unique value combinations to the renderer
for (status, tgt_ret), label in value_combinations.items():
value_string = f"{status}, {tgt_ret}" # Format as expected by the renderer
Target_Sym.renderer.addValue(value_string) # Add the value
Target_Sym.renderer.updateValue(value_string, {'label': label}) # Update the label
# Apply the symbology changes to the layer
Target_layer.symbology = Target_Sym
arcpy.AddMessage("Symbology Applied")
This looks like something that ChatGPT wrote up for me. The problem that I have with this are the functions that it calls up don't exist. For example I run into the the error: UniqueValueRenderer object has no attribute 'removeAllValues'
have you tried creating a .lyrx and use arcpy.management.ApplySymbologyFromLayer to update?
I have done this in the past but I am trying to go a different route for simplicity for the people I am making the tool for.
It should be "removeValues" UniqueValueRenderer—ArcGIS Pro | Documentation
See my post for the "removeAllValues" implementation, you have to supply that call with a valid mapping of existing group/value pairs.
There's also the odd thing with the updateRenderer function call for a Symbology object having a pseudo private/internal function header:
Which is odd as I think that's a function that a user should call, and not one that is only used internally by a Symbology object. It's also wrapped by maskargs which is a nearly inscrutable decorator function that apparently does some magic with the function argument names?
Yeah, ChatGPT is really bad for arcpy scripting and I would absolutely avoid it unless you know how to validate what it spits out. Lots of hallucinations because there isn't much public arcpy code on GitHub.
This is also a really complex workflow, As shown here:
from typing import Literal, TypeAlias, Union
from arcpy.mp import ArcGISProject
from arcpy._renderer import Base_renderer, UniqueValueRenderer, SimpleRenderer, Graduated_colors_renderer, Unclassed_colors_renderer, Graduated_symbols_renderer
from arcpy._symbology import Symbology, ItemGroup, Item
from arcpy._mp import Layer, Map
RendererName: TypeAlias = Literal[
'GraduatedColorsRenderer',
'GraduatedSymbolsRenderer',
'UnclassedColorsRenderer',
'SimpleRenderer',
'UniqueValueRenderer']
RendererType: TypeAlias = Union[
Base_renderer,
UniqueValueRenderer,
SimpleRenderer,
Graduated_colors_renderer,
Unclassed_colors_renderer,
Graduated_symbols_renderer]
def get_symbology(layer: Layer) -> Symbology:
return layer.symbology
def get_renderer(symbology: Symbology) -> RendererType:
return symbology.renderer
def set_renderer(symbology: Symbology, renderer: RendererName) -> RendererType:
symbology._updateRenderer(renderer)
return get_renderer(symbology)
def get_unique_value_renderer_values(renderer: UniqueValueRenderer) -> dict[str, list[str]]:
values: dict[str, list[str]] = {}
groups: list[ItemGroup] = renderer.groups
for group in groups:
items: list[Item] = group.items
values[group.heading] = [item.label for item in items]
return values
def remove_all_values(renderer: UniqueValueRenderer):
renderer.removeValues(get_unique_value_renderer_values(renderer))
def add_values(renderer: UniqueValueRenderer, values: dict[str, list[str]]) -> dict[str, list[str]]:
return renderer.addValues(values) or values
def main():
project = ArcGISProject(r"Path\To\Project")
_map: Map = project.listMaps()[0]
layer: Layer = _map.listLayers()[0]
symbology = get_symbology(layer)
renderer: UniqueValueRenderer = set_renderer(symbology, 'UniqueValueRenderer')
remove_all_values(renderer)
new_values = {}
add_values(renderer, new_values)
project.save()
return
if __name__ == "__main__":
main()
The arcpy code that we're using here is meant to be pseudo private, so I'm not sure how stable it will be, but it seems to at least update things. If this still doesn't work, you'll probably have to start digging into CIM definitions.
Logically everything you wrote out makes sense. I just don't understand why the simple addValues doesn't work. Here is how far I got. I will start digging into what you sent me to see if it works, but any insight into this? It doesn't result in an error but it doesn't result in the new symbol being made.
##Creates a list of layers that have "Buff" in their name
Target_layer= map_view.listLayers("Targets")[0]
arcpy.AddMessage("Target Layer Found")
Target_Sym= Target_layer.symbology
arcpy.AddMessage("Symbology Accessed")
Target_Sym.updateRenderer('UniqueValueRenderer')
arcpy.AddMessage("Update Renderer")
Target_Sym.renderer.fields= ['Status', 'tgt_ret']
arcpy.AddMessage("Renderer Fields Set")
StatusGroup= Target_Sym.renderer.groups[0]
StatusGroup.heading="Target Status"
Target_Sym.renderer.removeValues({"Target Status": ["CLOSED, Practice"]})
Target_Sym.renderer.removeValues({"Target Status": ["CLOSED, No Drop"]})
Target_Sym.renderer.removeValues({"Target Status": ["OPEN, No Drop"]})
Target_Sym.renderer.removeValues({"Target Status": ["OPEN, Practice"]})
Target_Sym.renderer.removeValues({"Target Status": ["OPEN, HE"]})
Target_layer.symbology = Target_Sym
string_pairs= [['OPEN', 'HE'],['OPEN', 'No Drop'],['OPEN', 'Practice']]
sliced_pairs = string_pairs[:3]
arcpy.AddMessage(sliced_pairs)
# Add the new values to the renderer
Target_Sym.renderer.addValues({"Target Status": sliced_pairs})
Target_layer.symbology = Target_Sym
Look at the type hint for my add_values function
def add_values(..., values: dict[str, list[str]]) -> ... : ...
You are currenty passing the addValues function
dict[str, list[list[str, ...]]]
Which is an incorrect format.