I was recently working on a project in QGIS 3 with a member of UVA Health's Oncology department. This person wanted to take a set of patient data (after identifying info had been removed) and after doing some other stuff, apply a graduated color scheme to the results, shading them from light to dark based on intensity.
You can find a sample dataset for this project here:
https://github.com/epurpur/PyQGIS-Scripts/blob/master/TestZipCodes.zip
This is a shapefile with a handful of zip codes in Virginia.
After looking around online I found some documentation and examples, but some were for older versions of the software. But I did find a nice current example in the PyQGIS Developer's Cookbook:
https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/vector.html
My code an example is basically a simplified version of the above link with some more description of particular features. Anyway, lets start from the top. If you've downloaded the sample layer, you have a shapefile with zip codes and in that layer's attribute table is a column called 'Patient_Da'. This is short for "Patient Data" and shows a count of number of patients by zip code. Here is the full code:
uri = '/Users/ep9k/Desktop/TestZipCodes/TestZipCodes.shp'
join_layer = iface.addVectorLayer(uri, 'Patients by Zip Code', 'ogr')
target_field = 'Patient_Da'
def apply_graduated_symbology():
"""Creates Symbology for each value in range of values.
Values are # of patients per zip code.
Hard codes min value, max value, symbol (color), and label for each range of
values. Then QgsSymbolRenderer takes field from attribute table and item from
myRangeList and applies them to join_layer. Color values are hex codes,
in a graduated fashion from light pink to black depending on intensity"""
myRangeList = []
symbol = QgsSymbol.defaultSymbol(join_layer.geometryType())
symbol.setColor(QColor("#f5c9c9"))
myRange = QgsRendererRange(0, 3, symbol, 'Group 1')
myRangeList.append(myRange)
symbol = QgsSymbol.defaultSymbol(join_layer.geometryType())
symbol.setColor(QColor("#000000"))
myRange = QgsRendererRange(3.1, 6, symbol, 'Group 2')
myRangeList.append(myRange)
myRenderer = QgsGraduatedSymbolRenderer(target_field, myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRenderer.Custom)
join_layer.setRenderer(myRenderer)
print(f"Graduated color scheme applied")
apply_graduated_symbology()
First, I want to show you what this would look like using the "symbology" tab of the GUI in QGIS. This is simple enough to do.
In the first three lines I created a uri which is the path to where my file is stored. You should modify your pathname accordingly. Then, I create the join_layer object and add the layer to my map. Finally, the target_field object is the 'Patient_Da' column in the attribute table of my join_layer, which is the sample data set:
uri = '/Users/ep9k/Desktop/TestZipCodes/TestZipCodes.shp'
join_layer = iface.addVectorLayer(uri, 'Patients by Zip Code', 'ogr')
target_field = 'Patient_Da'
Now, I created a function called apply_graduated_symbology() with no arguments. You can see from my docstring that this creates symbology for each value in a range of values. I specify this range of values and create a custom symbol for each. In the next few lines, I create a list object called myRangeList and set it to an empty list. This will hold the symbol for each range of values.
Next, I create a variable called symbol. symbol first stores the geometry type of the layer, which in this case is a polygon. Yours might be a line or point (marker). Insert a print statement here if you want to determine what yours is. Then, I set the color for this geometry type. I manually specified this color using a hex string ("f5c9c9"). This hex string is a pinkish color.
Then, create myRange. This creates a QgsRendererRange which takes the following arguments: (min value, max value, color, label). You can see that 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 if needed. Lastly, I append this to the myRangeList
symbol = QgsSymbol.defaultSymbol(join_layer.geometryType())
symbol.setColor(QColor("#f5c9c9"))
myRange = QgsRendererRange(0, 3, symbol, 'Group 1')
myRangeList.append(myRange)
Notice I did this sequence twice, for two ranges of values. The first is for 0-3 patients, the second is for 4-6 patients. You can add as many breaks as you like here. I am only using two for this simple example. Anyway, myRangeList now has two objects in it. The last few lines implement the code we've written. myRenderer creates an object which stores the QgsGraduatedSymbolRenderer object with the field name (target_field from above) and myRangeList (which stores the QgsRendererRange values for each break as arguments). Then we define the mode as "Custom". You could also use other modes such as Natural Breaks or Jenks. Lastly, you must render your layer and apply the graduated symbology. join_layer.setRenderer(myRenderer) does this. Take a look at the last few lines of code again:
myRenderer = QgsGraduatedSymbolRenderer(target_field, myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRenderer.Custom)
join_layer.setRenderer(myRenderer)
print("Graduated color scheme applied")
apply_graduated_symbology()
I finish with a print statement telling me this function ran and then execute the function outside the function block.
So that's really all there is to it. I went through some head scratching moments trying to follow documentation and various StackOverflow threads and eventually put the pieces together. I wrote this because I really couldn't find a great example resource to use so hopefully this serves as that for you.
Erich Purpur
Research Librarian for Sciences & Engineering
University of Virginia Library
December 07, 2018
For questions or clarifications regarding this article, contact statlab@virginia.edu.
View the entire collection of UVA Library StatLab articles, or learn how to cite.