3

I'm trying to apply a color ramp to a QgsGraduatedSymbolRenderer in PyQGIS 3 (QGIS 3.8) using the following code:

Getting the QgsGradientColorRamp:

myStyle = QgsStyle().defaultStyle()
defaultColorRampNames = myStyle.colorRampNames()
ramp = myStyle.colorRamp(defaultColorRampNames[-6]) #Spectral color ramp

Applying it to the QgsGraduatedSymbolRenderer:

myRenderer = QgsGraduatedSymbolRenderer('', myRangeList) #A list of break values
myRenderer.setSourceColorRamp(ramp)
myVectorLayer.setRenderer(myRenderer)
myVectorLayer.triggerRepaint()

Unfortunately the layer still shows in its original colors without the color map being applied. Upon looking in properties, however, oddly enough the color map is applied partially. The color map selected is indeed the one chosen ('Spectral'), but this isn't applied to the values' symbols.If I then just click on the color ramp and reapply it, it works, but I'd like to achieve this all through PyQGIS. I looked at similar questions here and the QGIS3 Python API and couldn't find a solution.

enter image description here

  • 1
    Hi 15Step, do you need to include the field name in the QgsGraduatedSymbolRenderer statement - it only seems to have empty single quotes at the moment? – ian Nov 19 '19 at 21:17
  • You mention '#A list of break values', do you have any info about these? Have you specified these already? – Cushen Nov 20 '19 at 23:26
  • So the variable myRangeList includes a list of QgsRendererRanges, instantiated with these variables: myRange = QgsRendererRange(myMin, myMax, mySymbol, myLabel). I basically followed the example from the PyQGIS cookbook here: docs.qgis.org/testing/pdf/en/…, on page 37. – 15Step Nov 21 '19 at 9:15
3

I have been through the same experience before, and (I may be wrong) but as far as I am aware, it is not possible to programmatically set a default color ramp and have it automatically applied. As you discovered, doing setSourceColorRamp() just sets that color ramp in the symbology dialog- it doesn't apply it to the renderer range symbols.

You have to create each range, instantiate QgsSymbol() objects, set colors to them and pass those in to a QgsRendererRange() constructor, together with the range and legend values.

It's a bit hacky, but I knocked out the script below which will create a graduated symbology with 10 classes (like your example above) in a basic spectral color ramp. Just change the layer name to match yours:

layer = QgsProject().instance().mapLayersByName('Your_Layer_Name')[0]
vals = []
fld = 'AREA'
for f in layer.getFeatures():
    vals.append(f[fld])
# If you don't like these colors, change them out for ones you do, using hexcodes,
# RGB codes etc. as long as the items in this list are valid strings you
# can pass to a QColor constructor 
colors = ['#0011FF', '#0061FF', '#00D4FF', '#00FF66', '#00FF00', '#E5FF32', '#FCFC0C', '#FF9F00', '#FF3F00', '#FF0000']
lower = sorted(vals)[0]
upper = sorted(vals)[-1]
step = (upper-lower)/len(colors)
range_list = []
for c in colors:
    cat = [lower, lower+step, c]
    sym = QgsSymbol.defaultSymbol(layer.geometryType())
    sym.setColor(QColor(cat[2]))
    rng = QgsRendererRange(cat[0], cat[1], sym, '{0:.1f}-{1:.1f}'.format(cat[0], cat[1]))
    range_list.append(rng)
    lower = (lower+step)+0.1
renderer = QgsGraduatedSymbolRenderer(fld, range_list)
layer.setRenderer(renderer)
layer.triggerRepaint()

Below is the result of this script run on a sample dataset- IBRA Bioregions of Australia, obtaining the graduation values from the "SHAPE_Area" field.

enter image description here

Based on the comment by @Cushen below, you could obtain a range of color values from an existing styled layer with a graduated ramp of the required number of classes and read those colors into a list object with a simple list comprehension:

styled_layer = iface.activeLayer() # existing layer styled with desired color ramp
colors = [s.color().name() for s in styled_layer.renderer().symbols(QgsRenderContext())]
  • Nice @Ben W. I notice it is possible to extract the colors from an existing Graduated ramp. Those colours could be extracted and fed into you 'colors' array; there will be a need to interpolate to find the middle colors as only the breakpoints seem to be included right now (i.e. if you want 5 steps but only 3 colours exist in the ramp). To get colors from a renderer: symbols = myLayer.renderer().symbols(QgsRenderContext()) for s in symbols: print (symbols[-1].color().name()) – Cushen Nov 21 '19 at 3:15
  • Thanks @Cushen. Good idea to obtain a range of color codes for the different class values from an existing layer. I just found a color chart online, copied the hex codes and stuck them into a list! – Ben W Nov 21 '19 at 9:00
  • @BenW Very nice workaround, thx! It seems odd however that there isn't an easier way to do it following the 'official' examples from the PyQGIS cookbook. Also, the way to apply a different classification (equal intervals, natural breaks) described there also doesn't seem to work through PyQGIS, but that's a different question. – 15Step Nov 21 '19 at 9:19
2

I've put together a script using bits from @Ben W's answer as well as from this answer this answer to a different question.

This takes the following inputs:

  • Number of Classes (number_classes)
  • Filed to use in the ramp (fld)
  • Existing Color Ramp (colorRampNumber)
    • Optional Alternative: user specified start and end colors to use for a custom color ramp

This generates and applies a color ramp to the specified field of active layer with the number of classes chosen.

The colors are taken from the start and end colors of the specified preexisting ramp, intermediate colors are 'interpolated' thanks to the colour library; this might need to be downloaded.
Alternativly the user can specify the start and end colors themselves (c1 and c2).

Code here:

from qgis.PyQt.QtGui import QColor
import re
import colour
from colour import Color

#to interpolate colors:
def linear_color_interpolation(c1, c2, t):
    r = int(c1.red() + (c2.red() - c1.red()) * t)
    g = int(c1.green() + (c2.green() - c1.green()) * t)
    b = int(c1.blue() + (c2.blue() - c1.blue()) * t)
    return QColor(r, g, b)

layer = iface.activeLayer()
renderer = layer.renderer()
provider = layer.dataProvider()

#Specify inputs:
fld = 'roof_heigh'
colorRampNumber = 4
number_classes = 5

#COLOR RAMP STYLE:
myStyle = QgsStyle().defaultStyle()
defaultColorRampNames = myStyle.colorRampNames()
ramp = myStyle.colorRamp(defaultColorRampNames[colorRampNumber]) #Spectral color ramp


vals = []
for f in layer.getFeatures():
    vals.append(f[fld])
vals = (list(filter(None,vals))) #REMOVE NONE/NULL from list

idx = provider.fieldNameIndex(fld)
max = layer.maximumValue(idx)
min = layer.minimumValue(idx)
interval = (max - min)/(number_classes -1 )

#classes
sum = min
classes = []

for i in range(number_classes):
    tmp = int(round(sum, 0))
    classes.append(tmp)
    sum += interval

t_list = []
init = 0

for i in range(number_classes):
    t_list.append(init)
    init += 1./(number_classes-1)


#FIRST AND LAST COLORS FROM PREDEFINED RAMP
c1 = QColor(ramp.color(1).name())
c2 = QColor(ramp.color(-1).name())

## OR SPECIFY YOUR OWN START AND END COLORS:
#c1 = QColor('blue')
#c2 = QColor('yellow')
#c1 = QColor('#0000ff')
#c2 = QColor('#00ffff')


colors2 = list(Color(c1.name()).range_to(Color(c2.name()), number_classes))
colors3 = [ QColor(element.hex) 
            for element in colors2 ]
colors = [ linear_color_interpolation(c1, c2, t) for t in t_list ]
color_list = [ QgsColorRampShader.ColorRampItem(classes[i], colors3[i])  for i in range(number_classes) ]
# for col in color_list:
#     print (col.color.name())

lower = sorted(vals)[0]
lower = sorted(vals)[1] 
upper = sorted(vals)[-1]
step = (upper-lower)/len(colors)
grades = []

for c in colors:
    cat = [lower, lower+step, c]
    lower = (lower+step)+0.1
    grades.append(cat)
range_list = []

for i in grades:
    sym = QgsSymbol.defaultSymbol(layer.geometryType())
    sym.setColor(QColor(i[2]))
    rng = QgsRendererRange(i[0], i[1], sym, '{0:.1f}-{1:.1f}'.format(i[0], i[1]))
    range_list.append(rng)

renderer = QgsGraduatedSymbolRenderer(fld, range_list)

layer.setRenderer(renderer)
layer.triggerRepaint()

print ('\nCOLOR RAMP: %s' %(str(colorRampNumber)))
print ('COLOR 1: %s COLOR 2: %s' %(ramp.color(1).name(),ramp.color(-1).name()))
print ('MIN: %s MAX:%s' %(str(min), str(max)))
print ('NUMBER OF CLASSES: %s'%(str(number_classes)))
print('*** ALL DONE :D ***')

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.