PyQGIS 3 – Applying Graduated Renderer to a Polygon

pyqgispyqgis-3qgis-3

I'm trying to symbolize a polygon layer using a graduated color ramp, with no success.

In the attribute field of my layer I have a column: 'PatCNT' with integer values from 2 to 6 for each row.

I've been trying to adapt QGIS v2.x code from Applying graduated renderer in PyQGIS? or from QGIS python programming cookbook (also for QGIS 2.x)
I'm trying to use the examples from the PyQGIS cookbook describing how to use the graduated symbol renderer. I have pretty much copied the code from the cookbook example below and tried to adapt it to my case.

from qgis.PyQt import QtGui
from qgis.core import *

uri = '/Users/ep9k/Desktop/SandraMonson/TestZips.shp'

myVectorLayer = QgsVectorLayer(uri, 'testZipCodes', 'ogr')
myTargetField = 'PatCNT'
myRangeList = []
myOpacity = 1

#Make our first symbol and range
myMin = 2.0
myMax = 4.0
myLabel = 'Group 1'
myColor = QtGui.QColor(Qt.red)
mySymbol1 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
mySymbol1.setColor(myColor)
mySymbol1.setOpacity(myOpacity)
myRange1 = QgsRendererRange(myMin, myMax, mySymbol1, 
myVectorLayer.geometryType())
myRangeList.append(myRange1)

#now make another symbol and range
myMin = 4.1
myMax = 6.0
myLabel = 'Group 2'
myColor = QtGui.QColor(Qt.blue)
mySymbol2 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
mySymbol2.setColor(myColor)
mysymbol2.setOpacity(myOpacity)
myRange2 = QgsRendererRange(myMin, myMax, mySymbol2, myLabel)
myRangeList.append(myRange2)

myRenderer = QgsGraduatedSymbolRenderer('', myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRenderer.EqualInterval)
myRenderer.setClassAttribute(myTargetField)
myVectorLayer.setRenderer(myRenderer)
QgsProject.instance().addMapLayer(myVectorLayer)

I am just trying to have "group 1" in red and "group 2" in blue for now. Then I can customize it further in the future if I want.

Depending on what I change, I get one of many error messages…

Best Answer

I'm going to answer my own question in two ways.

First, I'll use the example from the PyQGIS Developer Cookbook. I initially struggled with this example but eventually got it working, so here is a simplified example below with some additional annotation and comments. I hard coded some values which you may want to adapt.

Here is the full code as an example:

uri = '/Users/ep9k/Desktop/SandraMonson/TestZips.shp'
join_layer = iface.addVectorLayer(uri, 'Patients by Zip Code', 'ogr')
target_field = 'PatCNT'

myRangeList = []

symbol = QgsSymbol.defaultSymbol(join_layer.geometryType())
symbol.setColor(QColor("#f5c9c9"))
myRange = QgsRendererRange(0, 1, symbol, 'Group 1')
myRangeList.append(myRange)

symbol = QgsSymbol.defaultSymbol(join_layer.geometryType())
symbol.setColor(QColor("#f97a7a"))
myRange = QgsRendererRange(1.1, 2, symbol, 'Group 2')
myRangeList.append(myRange)

myRenderer = QgsGraduatedSymbolRenderer(target_field, myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRenderer.Custom)

join_layer.setRenderer(myRenderer)

Let's break this example down and I'll start with the first three lines.

uri = '/Users/ep9k/Desktop/SandraMonson/TestZips.shp'
join_layer = iface.addVectorLayer(uri, 'Patients by Zip Code', 'ogr')
target_field = 'PatCNT'

The uri is the path to your layer. 'Patients by zip code' is the name given in the QGIS'S layer panel. 'ogr' is typically used with shapefiles. iface.addVectorLayer adds the layer given by the uri into your map. You could substitute this for QgsVectorLayer() to work with a layer already in the map. The target_field is the field you want to symbolize in the attribute table of your shapefile.

myRangeList = []

symbol = QgsSymbol.defaultSymbol(join_layer.geometryType())
symbol.setColor(QColor("#f5c9c9"))
myRange = QgsRendererRange(0, 1, symbol, 'Group 1')
myRangeList.append(myRange)

myRangeList will store symbology values for each group of data. symbol is a variable which stores the geometry type of the layer, which in my case is a polygon. Your's might be a line or point (marker). Insert a print statement here if you like to determine what yours is. Then, I set the color for this geometry type. I used a hex string here ("#f5c9c9") which is a pinkish color.

next, create myRange. This creates a QgsRendererRange which takes the following arguments: (min value, max value, color, label). You can see here I hard coded the min, max, and label. The color I already defined and stored in symbol. You can do something more advanced here to calculate the min and max. Lastly, I append this to myRangeList.

Notice I did this a second time in my example. myRangeList now contains two objects, the first is for values 0-1, the second is for values 1.1-2. You can make as many breaks in your dataset as you want here. This is a custom example (more below) but you could also use Natural Breaks, Jenks, etc.

myRenderer = QgsGraduatedSymbolRenderer(target_field, myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRenderer.Custom)

join_layer.setRenderer(myRenderer)

Now let's actually implement what we've done. myRenderer creates a object which stores the QgsGraduatedSymbolRenderer object with the field name (target_field) and myRangeList (which stores the QgsRendererRange values for each break) as arguments. Then, we define the mode as "custom". As stated earlier, you can use pre-existing modes such as Jenks, Natural Breaks, etc.

Lastly, you need to render your layer and apply the graduated symbology. join_layer.setRenderer(myRenderer) does this.

~~~~~~~~~~~~~~~~~~~~~~~~

Second, I found in this thread, which is for QGIS 2.x and older , which I adapted to 3.x, but serves as a really nice customized example with extra functions to validate geometry types, etc..

from PyQt5.QtGui import *

target_field = 'PatCNT'
layer = QgsVectorLayer('/Users/ep9k/Desktop/SandraMonson/TestZips.shp', 
'Fixed Divisions', 'ogr')

def validatedDefaultSymbol(geometryType):
    symbol = QgsSymbol.defaultSymbol(geometryType)
    if symbol is None:
        if geometryType == Qgis.Point:
            symbol = QgsMarkerSymbol()
        elif geometryType == Qgis.Line:
            symbol = QgsLineSymbol()
        elif geometryType == Qgis.Polygon:
            symbol = QgsFillSymbol()
    return symbol

def makeSymbologyForRange(layer, min, max, title, color):
    symbol = validatedDefaultSymbol(layer.geometryType())
    symbol.setColor(color)
    range = QgsRendererRange(min, max, symbol, title)
    return range

def applySymbologyFixedDivisions(layer, field):
    rangeList = []
    rangeList.append( makeSymbologyForRange(layer, 2, 4, '2-4', 
QColor("Green") ) )
    rangeList.append( makeSymbologyForRange(layer, 4.1, 6, '4-6',  
QColor("Purple") ) )
    renderer = QgsGraduatedSymbolRenderer(field, rangeList)
    renderer.setMode(QgsGraduatedSymbolRenderer.Custom)
    layer.setRenderer(renderer)

if layer.isValid():
    applySymbologyFixedDivisions(layer, target_field)
    QgsProject.instance().addMapLayer(layer)

I found the lack of QGIS 3.x examples difficult to navigate, so I hope this works for you! I'm no expert so please edit this response and comment as necessary.