Set Labels from Expression in PyQGIS – Text on Symbols Vector Legend

legendpyqgis

I am trying to programmatically set vector legend Text on Symbols with PyQGIS. This can be manually performed from Layer Properties -> Legend -> Text on Symbols -> Set Labels from Expression… e.g. using an attribute "MAP_SYMBOL", refer screenshot.

Screenshot of Layer Properties -> Legend

The relevant C++ gui widget is QgsVectorLayerLegendWidget

But so far I have been unsuccessful replicating the logic that starts with QgsVectorLayerLegendWidget::labelsFromExpression() in PyQGIS, is this even possible or realistic? There is a lot going on there to apply the expression that does not appear to be exposed via the PyQGIS bindings.

There are some tantalizing PyQGIS-accessible classes e.g. QgsLegendSymbolItem and QgsDefaultVectorLayerLegend but I can't see how to modify the Text on Symbols and then successfully apply these classes to the vector layer which is not straightforward based on the C++ code.

Related: Adding text on symbols using PyQGIS

Best Answer

Here is a basic example which you can test. This is assuming you want to use the field "MAP_SYMBOL" as per your question as the expression to set the text on symbol.

lyr = iface.activeLayer()
canvas = iface.mapCanvas()

# Retrieve the current symbol node text format
tree_view = iface.layerTreeView()
model = tree_view.layerTreeModel()
root = model.rootGroup()
ltl = root.findLayer(lyr)
nodes = model.layerLegendNodes(ltl)
text_format = nodes[0].textOnSymbolTextFormat()

content = {}
context = QgsRenderContext.fromMapSettings(canvas.mapSettings())
# Set your expression here
expr = QgsExpression("MAP_SYMBOL")
expr.prepare(context.expressionContext())

r = lyr.renderer().clone()

r.startRender(context, lyr.fields())

for f in lyr.getFeatures():
    context.expressionContext().setFeature(f)
    keys = set(r.legendKeysForFeature(f, context))
    for key in keys:
        if key in content:
            continue
        label = expr.evaluate(context.expressionContext())
        if label:
            content[key] = label

r.stopRender(context)

legend = QgsDefaultVectorLayerLegend(lyr)
legend.setTextOnSymbolEnabled(True)
legend.setTextOnSymbolContent(content)
legend.setTextOnSymbolTextFormat(text_format)
lyr.setLegend(legend)
legend.itemsChanged.emit()

This bit of code is basically put together from bits picked out of the QgsVectorLayerLegendWidget C++ source code, primarily the methods: QgsVectorLayerLegendWidget::labelsFromExpression() and QgsVectorLayerLegendWidget::applyToLayer().

As an example, I tested on a soil layer with a categorized renderer, classified with the "MAPUNIT" field.

With the script above modified only by changing the line which declares the expression to:

expr = QgsExpression("SOIL_TYPE")

The result after running the code was:

enter image description here