I am trying to find a way to remove specific legend nodes in a layout legend for a vector layer styled with categorized symbology using the PyQGIS-3 API. For example, in a QGIS project I have one categorized layer, a few grouped layers and a number of other ungrouped, vector and raster layers.
How can I remove one or more of the legend nodes belonging to the categorized layer using PyQGIS?
It is a trivial task to remove them manually via item properties in the layout interface but I can not find any way to accomplish this using PyQGIS. The layout legend (annotated with model rows, groups, parent indices and child node positions) looks like this:
If I access the legend model and get its row count like this:
d = iface.openLayoutDesigners()[0]
l = d.layout()
legend = l.selectedItems()[0]
m = legend.model()
print(m.rowCount())
the row count method returns 8 rows. This tells me that neither the category nodes, nor the child nodes of the group are considered to be separate rows in the model. However the legend nodes of the ungrouped layers and the child nodes of the grouped layers can be removed quite easily. First an ungrouped layer:
# remove rows with no parent
d = iface.openLayoutDesigners()[0]
l = d.layout()
legend = l.selectedItems()[0]
m = legend.model()
legend.setAutoUpdateModel(False)
m.removeRows(4, 1) # pass row index & number of rows to remove
legend.refresh()
Then a member of the ‘Collecting Targets’ group:
# remove rows from a group
d = iface.openLayoutDesigners()[0]
l = d.layout()
legend = l.selectedItems()[0]
m = legend.model()
idx = m.index(1, 0) # get index of parent
legend.setAutoUpdateModel(False)
m.removeRows(1, 1, idx) # pass position, number of rows to remove & index of parent
legend.refresh()
However, if I use the code above but get the index of the categorized layer and pass that to the removeRows()
method as the 3rd argument (parent), no rows are removed. There is no error encountered- it just does not work.
I can use the method legendRootRowCount()
to return each category of the categorized layer as a row.
I can also access the nodes of the categorized layer and store the parent and its nodes in a dictionary like so:
d = iface.openLayoutDesigners()[0]
l = d.layout()
legend = l.selectedItems()[0]
layer_and_nodes = dict()
m = legend.model()
idx = m.index(0, 0)
n = m.index2node(idx)
nodes = m.layerLegendNodes(n)
layer_and_nodes[n.name()] = nodes
print(layer_and_nodes)
This returns:
{'fs2019shp': [<qgis._core.QgsLayerTreeModelLegendNode object at 0x00000013809CFE58>, <qgis._core.QgsLayerTreeModelLegendNode object at 0x00000013809CFEE8>, <qgis._core.QgsLayerTreeModelLegendNode object at 0x00000013809CFF78>, <qgis._core.QgsLayerTreeModelLegendNode object at 0x00000013809E2048>, <qgis._core.QgsLayerTreeModelLegendNode object at 0x00000013809E20D8>, <qgis._core.QgsLayerTreeModelLegendNode object at 0x00000013809E2168>, <qgis._core.QgsLayerTreeModelLegendNode object at 0x00000013809E21F8>]}
But I can’t work out any way of removing or filtering those QgsLayerTreeModelLegendNode objects.
I tried:
llns = [v for k, v in layer_and_nodes.items()][0]
m.filterLegendNodes(llns)
I suspect I am not implementing this method correctly as this also fails with no errors.
I try to force a refresh of layer nodes for the categorized layer after both the removeRows()
and the filterLegendNodes()
calls using m.refreshLayerLegend(n)
which does not have any effect.
I have scoured the API documentation and searched GIS.StackExchange for similar questions. My research also introduced me to the concept of QT model/ view programming (of which I admit I was completely ignorant) but I have not been able to find a solution.
How can I remove a legend node of a categorized layer from a QgsLegendModel using PyQGIS?