11

I am trying to generate maps in PDF files by using a pre-defined .qgz layout. I am already able to load layers and adjust color scale, etc. Since the legend is synchronized with the map layers, all the layers I have loaded are listed in the legend.

Using PrintComposer in QGIS I can turn off synchronization and remove the unwanted items in legend. Is there a way to do it automatically in Python? I tried to use QgsLegendModel, but it also removes the layers from map canvas.

unwanted items in legend.

projectInstance= QgsProject.instance()
projectLayoutManager = projectInstance.layoutManager()

for comp in projectLayoutManager.printLayouts():
    for item in comp.items():
        if(isinstance(item, QgsLayoutItemLegend)):                
            model = item.model()
            while model.rowCount() > 1:
                 model.removeRow(0)
                      
            break

3 Answers 3

10

This is code adapted for QGIS 3 from this answer. It adds all layers except one specified. You could swap this out for a list, or conversely, use the layers you want to keep in rather than those you want to leave out.

l = QgsProject.instance().layoutManager()
    
for layout in l.printLayouts():    
    legend = QgsLayoutItemLegend(layout)
    root = QgsLayerTree()
    for lyr in iface.mapCanvas().layers():
        if lyr.name() != 'Unwanted layer':  
            root.addLayer(lyr)
        legend.model().setRootGroup(root)
        layout.addItem(legend)

In place of if lyr.name() != 'Unwanted layer': you could use

if lyr.name() not in [list, of, unwanted, layer, names]:

or

if lyr.name() in [list, of, wanted, layer, names]:
4
  • iface.mapCanvas().layers() returns an empty list when run within the script. However when I paste it in Python console it returns correct collection of layers. I guess there is an asynchronous update for which I should wait for? Commented Dec 18, 2018 at 13:54
  • Are you using a standalone script or one in the Editor in the Python console?
    – Matt
    Commented Dec 19, 2018 at 12:43
  • I am using Python Editor (within QGIS 3.4) Commented Dec 19, 2018 at 12:50
  • That is strange. Mine works as desired. It won't include layers which are unchecked in the layers panel, however.
    – Matt
    Commented Dec 19, 2018 at 12:53
8

@Matt, your solution started to work after I've forced qt to process the buffered events, before iterating through layers. Thank you for your hint.

from PyQt5 import QtWidgets

inst = QtWidgets.QApplication.instance()
qapp = QtWidgets.qApp
qapp.processEvents()

l = QgsProject.instance().layoutManager()

for layout in l.printLayouts():    
    legend = QgsLayoutItemLegend(layout)
    root = QgsLayerTree()
    for lyr in iface.mapCanvas().layers():
        if lyr.name() != 'Unwanted layer':  
            root.addLayer(lyr)
        legend.model().setRootGroup(root)
        layout.addItem(legend)
2

May be not a direct answer to a question but I think my case could also be met by someone else.

I was trying to find a way to remove items of layers in composer legend. If layer has a rule-based or unique values symbology type sometimes you need to filter them in a legend. I didn’t want to reclassify or re-order symbology of initial layer, just keep it permanent but change it in composer’s legend. In general, I wanted to emulate a minus button click in legend settings.

enter image description here

Recently I have found a solution thanks to another answer from StackOverflow: https://stackoverflow.com/a/67223630/8776330.

It is based on use of QgsMapLayerLegendUtils and legend model. In my case there is a single layout with only one legend item containing two layers. The first layer multi_layer2 has four symbology nodes and the goal is to remain only two of them.

Code:

# define layer which legend has to be modified
layer = QgsProject.instance().mapLayersByName('multi_layer2')[0]

# get a layout and legend item
project = QgsProject.instance()         
layout_manager = project.layoutManager()
layouts_list = layout_manager.printLayouts()
layout = layouts_list[0]
legend = next(filter(lambda n: type(n)==QgsLayoutItemLegend, layout.items()))

# get legend items 
model = legend.model()
layer_tree = legend.model().rootGroup().findLayer(layer) # QgsLayerTreeLayer object
symbols = model.layerOriginalLegendNodes(layer_tree) # collection of layer legend nodes

# assuming we want to keep only first and third node
symbols_to_remain = [0,2]

# setting a new order and applying changes
QgsMapLayerLegendUtils.setLegendNodeOrder(layer_tree, symbols_to_remain)
model.refreshLayerLegend(layer_tree)

Result:

enter image description here

1
  • Thanks it help a lot Commented Jan 12 at 9:57

Your Answer

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

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