Select to view content in your preferred language

UniqueValueRenderer: Grouping multiple values into single symbol

234
9
4 weeks ago
CodyDeVries1
Emerging Contributor

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")

 

0 Kudos
9 Replies
TonyAlmeida
MVP Regular Contributor

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")
CodyDeVries1
Emerging Contributor

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'

0 Kudos
TonyAlmeida
MVP Regular Contributor

have you tried creating a .lyrx and use arcpy.management.ApplySymbologyFromLayer to update?

0 Kudos
CodyDeVries1
Emerging Contributor

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. 

0 Kudos
AlfredBaldenweck
MVP Regular Contributor
HaydenWelch
MVP

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:

HaydenWelch_0-1736963125356.png

 

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?

0 Kudos
HaydenWelch
MVP

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.

 

0 Kudos
CodyDeVries1
Emerging Contributor

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



0 Kudos
HaydenWelch
MVP

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.

0 Kudos