0

In pyQGIS I have added a vector layer based on a CSV file. The CSV file contains several fields: x , y, Capacity, Category.

I can render the map with simple points at the given (x,y) coordinates.

What I would like to do give each point a circular symbol with a diameter based on the Capacity value and a color based on the Category value. This is my current code for changing the color:

project = QgsProject.instance() 
project.read("filename.qgz")
uri='file:///C:/Till//csv_file.csv?delimiter=,&yField=y&xField=x'

new_layer= QgsVectorLayer(uri, "new", "delimitedtext")
project.addMapLayer(new_layer)

fni = new_layer.fields().indexFromName('Category')
unique_cats = new_layer.uniqueValues(fni)
categories=[]

for unique_value in unique_cats:
    symbol = QgsSymbol.defaultSymbol(new_layer.geometryType())

    layer_style = {}
    layer_style['color'] = '100, 100, 100' # just for testing
    layer_style['outline'] = '#000000'
    symbol_layer = QgsSimpleFillSymbolLayer.create(layer_style)

    if symbol_layer is not None:
        symbol.changeSymbolLayer(0, symbol_layer)

    # create renderer object
    category = QgsRendererCategory(unique_value, symbol, str(unique_value))
    # entry for the list of category items
    categories.append(category)

renderer = QgsCategorizedSymbolRenderer('EdgeRef', categories)

new_layer.setRenderer(renderer)

However when I render this layer, it just shows empty. What is going wrong? And how can I add the diameter feature to the geometry?

2 Answers 2

1

For symbology of points you have to use QgsMarkerSymbol objects (not QgsSimpleFillSymbolLayer objects). So, following code (it has my path to csv file) can be used for defining categorized renderer (QgsCategorizedSymbolRenderer) with symbols based on several categories in points.

project = QgsProject.instance() 
#project.read("/home/zeito/Desktop/filename.qgz") #not necessary
uri = "file:///home/zeito/Desktop/csv_file.csv?delimiter={}&xField={}&yField={}".format(",", "x", "y")

new_layer= QgsVectorLayer(uri, "new", "delimitedtext")
project.addMapLayer(new_layer)

feats = [ feat for feat in new_layer.getFeatures() ]

n = len(feats)

fields_names = new_layer.fields().names()
idx = fields_names.index('Capacity')

capacity = [ feat.attribute(fields_names[idx]) for feat in feats]

colors = [ 'blue', 'cyan', 'magenta', 'red', 'green' ]
labels = [ 'a', 'b', 'c', 'd', 'e']

circle_symbol = [ QgsMarkerSymbol.createSimple({'color': colors[i], 
                                                'size': str(capacity[i]), 
                                                'outline_color': 'black'}) 
                                                for i in range(n) ]

list = [ QgsRendererCategory(capacity[i], 
         circle_symbol[i].clone(), 
         labels[i], 
         True)
         for i in range(n) ]

renderer = QgsCategorizedSymbolRenderer("Capacity", list)

new_layer.setRenderer(renderer)

new_layer.triggerRepaint()

iface.layerTreeView().refreshLayerSymbology(new_layer.id())

Loaded CSV looks as in following image:

enter image description here

After running above code, result was as follows:

enter image description here

On the other hand, you can also do this task with single symbols in this way:

project = QgsProject.instance() 
#project.read("/home/zeito/Desktop/filename.qgz") #not necessary
uri = "file:///home/zeito/Desktop/csv_file.csv?delimiter={}&xField={}&yField={}".format(",", "x", "y")

new_layer= QgsVectorLayer(uri, "new", "delimitedtext")
project.addMapLayer(new_layer)

symbol = QgsMarkerSymbol.createSimple({'name':'circle',
                                       'size_expression': 'Capacity',
                                       'color_expression':'color',
                                       'outline_width':'0'})

renderer = new_layer.renderer()
renderer.setSymbol(symbol)
new_layer.triggerRepaint()

iface.layerTreeView().refreshLayerSymbology(new_layer.id())

After running above code in Python Console of QGIS 3, I got an equivalent result:

enter image description here

However, single symbol is not split in categories.

1

Another option is to create the renderer categories based on the unique values in your 'Category' field, then set your point size by setting a data defined size property from your 'Capacity' field.

Try the code below. I have not tested the loading of the csv file since I am assuming that is working ok.

from random import randrange

project = QgsProject.instance() 
project.read("filename.qgz")
uri = 'file:///C:/Till//csv_file.csv?delimiter=,&yField=y&xField=x'

new_layer = QgsVectorLayer(uri, "new", "delimitedtext")
project.addMapLayer(new_layer)
fni = new_layer.fields().indexFromName('Category')
unique_cats = new_layer.uniqueValues(fni)
categories = []
for unique_value in unique_cats:
    R = int(randrange(1, 255))
    G = int(randrange(1, 255))
    B = int(randrange(1, 255))
    layer_style = {}
    layer_style['color'] = '{}, {}, {}'.format(R, G, B)
    layer_style['outline'] = '#000000'
    marker_symbol = QgsMarkerSymbol.createSimple(layer_style)
    category = QgsRendererCategory(unique_value, marker_symbol.clone(), str(unique_value))
    categories.append(category)
renderer = QgsCategorizedSymbolRenderer('Category', categories)

if renderer is not None:
    # Set Data Defined Size from field
    prop = QgsProperty()
    prop.setField('Capacity')
    for f in new_layer.getFeatures():
        renderer.startRender(QgsRenderContext(), new_layer.fields())
        sym = renderer.symbolForFeature(f, QgsRenderContext())
        sym.setDataDefinedSize(prop)
        renderer.stopRender(QgsRenderContext())
    new_layer.setRenderer(renderer)

new_layer.triggerRepaint()

This approach creates a random RGB code for each unique value in your 'Category' field. I tested this on a point layer with my own attributes as seen in the image below.

enter image description here

Below was the result when creating categories based on unique values in the 'Phenology' field and assigning a randomly generated color to each category:

enter image description here

However if you have actual color values in your field e.g. 'Red', 'Green', 'Yellow' or '225, 25, 5' etc. you could modify the script to create the color directly from the attribute value:

for unique_value in unique_cats:
    layer_style = {}
    layer_style['color'] = str(unique_value)

Below is the result of using this approach on my test layer, where I created the categories based on unique values in the 'Col_name' field and created the color directly from the value.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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