Select to view content in your preferred language

GraduatedColorsRenderer Not honoring data

699
6
07-09-2023 08:55 PM
NickAtEsriAU
New Contributor II

Hi All,

I am trying to automate the addition of a polygon layer to a Map in ArcGIS Pro (2.9, 3.1) and apply a consistent symbology to the layer based an attribute (TotalCanop), which is a floating point field.  I don't want to apply symbology from a layer file as I want this code to be part of an ".atbx" with the code embedded an no other files required.

Below is the code (which is a sub part of larger script).  When it is applied to the attached shapefile only one class ("<=5.0% Canopy cover") is displayed for the layer in the table of contents.  The class that example polygon should have is "<=15.0% Canopy cover" .  In fact my preference would be to have the layer symbology show all classes in the script no matter what the range of values in the "TotalCanop" field.

Any help in fixing this would be greatly appreciated. The code I am sharing is based on help supplied by @Anonymous User to solve this post:https://community.esri.com/t5/python-questions/arcpy-update-raster-layer-symbology-using/m-p/1306854#M68119

 

import arcpy
def total_canopy_symbology(layer_filter, map_filter):
    aprx = arcpy.mp.ArcGISProject("CURRENT")
    m = aprx.listMaps(map_filter)[0]
    l = m.listLayers(layer_filter)[0]
    breaks = {0: {'upper': 5.0, 'color': [225, 225, 225, 100]},
                  1: {'upper': 10.0, 'color': [204, 204, 204, 100]},
                  2: {'upper': 15.0, 'color': [156, 156, 156, 100]},
                  3: {'upper': 20.0, 'color': [130, 130, 130, 100]},
                  4: {'upper': 30.0, 'color': [112, 168, 0, 100]},
                  5: {'upper': 100.0, 'color': [92, 137, 68, 100]}
                  }
    arcpy.AddMessage("Adding CEAs symbolised by % canopy to the Map")

    sym = l.symbology
    sym.updateRenderer('GraduatedColorsRenderer')
    sym.renderer.classificationField = "TotalCanop"
    sym.renderer.breakCount = len(breaks)
    strSta = f"Rendered applied to {l.name}\n"
    for i, brk in enumerate(sym.renderer.classBreaks):
        brk.upperBound = breaks[i]['upper']
        brk.label = "<=" + str(breaks[i]['upper']) + "% Canopy cover"
        brk.symbol.color = {'RGB': breaks[i]['color']}
        strCur = f"Adding break at {breaks[i]['upper']} with colour {breaks[i]['color']}\n"
        strSta += strCur
    l.symbology = sym
    print(strSta)
    return True
total_canopy_symbology("*CEAs", "Map")

 

 

0 Kudos
6 Replies
by Anonymous User
Not applicable

I think this is a bug and the code example in the documentation for GraduatedColorsRender will result in an error if the layer's current symbology is single symbol. The code example doesn't include setting the renderer to sym.updateRenderer('GraduatedColorsRenderer') so the sym doesn't have the classBreaks list property and there is nothing to iterate over:

JeffK_3-1688997066830.png

You'll get: AttributeError: 'SimpleRenderer' object has no attribute 'classBreaks' at

for brk in sym.renderer.classBreaks:

 

When I add the updateRenderer to 'GraduatedColorsRenderer' in code, it does not set the breakCount.  It remains 1 regardless of any number is set to using sym.renderer.breakCount = len(breaks) or sym.renderer.breakCount = 6

JeffK_0-1688996047891.png

Manually changing the layer render to Graduated in Pro, it shows 5 classes (I updated the test shapefile to have more polygons that would be within the ranges for testing) in the UI:

JeffK_1-1688996709234.png

But in code (debugger) it still defaults to 1 for breakCount and the classBreaks:

JeffK_2-1688996912480.png

Maybe there is a way around this, but it seems like a bug to me.

NickAtEsriAU
New Contributor II

@Anonymous User  again thanks for your input.  I have spent so much time on this with very mixed results. 

I have uploaded another shapefile as an example where the codes works as you would expect, which just add to the frustration.  Just change the last line to:

total_canopy_symbology("*Tess*", "Map")
0 Kudos
by Anonymous User
Not applicable

Ah! thanks... so it seems that your script is working on my end?

JeffK_1-1689000394036.png

Maybe its somewhere else in the code that is causing the issue?

0 Kudos
NickAtEsriAU
New Contributor II

Yes but I am confused about the inconsistent behavior between layers.  It would almost seem like you need values across all classes of the renderer in order for the layer to display as expected.

0 Kudos
NickAtEsriAU
New Contributor II

If I manually import the symbology from the Tessellation layer to the CEA layer then the CEA layer displays as expected.  I really don't want to have to start playing around with the CIM as this should work through ArcPy.

0 Kudos
by Anonymous User
Not applicable

I don't know how this escaped me, I think I was focused on something else at the time. I think the fault is in it wont let you create more breaks/counts than features. Stepping through the code it does not set the sym.renderer.breakCount to 6 for the CEA layer so when you iterate over its list of 1 item, i is 0 and it is setting the 0 value:

brk.upperBound = breaks[i]['upper']
brk.label = "<=" + str(breaks[i]['upper']) + "% Canopy cover"
brk.symbol.color = {'RGB': breaks[i]['color']}
strCur = f"Adding break at {breaks[i]['upper']} with colour {breaks[i]['color']}\n"

The loop then stops there and that is all it does. You can test this by doing i+1 and it will show the value for 1 in the toc.

But, if ran on the other featureclasses with more than 1 feature, it seems to work ok up to the count of features. With that in mind you can change the code to get the value from the TotalCanop field and go from there, for example:

 if len(sym.renderer.classBreaks) == 1:
        value = [row[0] for row in arcpy.da.SearchCursor(l, ['TotalCanop'])][0]
        for k, v in breaks.items():
            if v['upper'] > value:
                brk = sym.renderer.classBreaks[0]
                brk.label = "<=" + str(v['upper']) + "% Canopy cover"
                brk.symbol.color = {'RGB': v['color']}
                strCur = f"Adding break at {v['upper']} with colour {v['color']}\n"
                strSta += strCur
                break

    else:
        for i, brk in enumerate(sym.renderer.classBreaks):
            brk.upperBound = breaks[i]['upper']
            brk.label = "<=" + str(breaks[i]['upper']) + "% Canopy cover"
            brk.symbol.color = {'RGB': breaks[i]['color']}
            strCur = f"Adding break at {breaks[i]['upper']} with colour {breaks[i]['color']}\n"
            strSta += strCur

gives this, with <= 15.0% for the CEAs:

JeffK_1-1689270000966.png

You can add conditionals for featureclasses that have any feature counts below the 6 in the breaks and go from there. Hope this helps-

 

 

 

0 Kudos