0

Is there a way to count legend items displayed in the print composer ? I would like to adjust by expression the number of columns of my legend based on the number of legend items.

Example : if there are 6 or less legend items I want my legend to be displayed on 3 columns. If the number of items exceeds 6 then I want my legend to be displayed on 4 columns.

You can find below a screenshot for illustration purposes which features 7 legend items with a column count of 4.

Thanks

enter image description here

  • I count only 3 column in your 7 items exemple screenshot, also it doesn't seem that a fourth column would fit in your layout. That being said it is possible to use expression to control the number of column of legend item, so you just need to know how many " Occupation du sol" category you will display on your map. if you use an atlas have a look at the Aggregate "count_distinct" function and on how to filter by atlas content – J.R Jun 18 at 14:24
  • @J.R oddly enough a column count of 4 gives the result shown in the screenshot, don't ask me why. I've tried using this formula : aggregate('test_rpg_17','count_distinct',"CODE_GROUP",intersects($geometry,map_get(item_variables('carte3'),'map_extent'))) but it returns 9 instead of 7. It may be needlessly complicated too, I'm no expert at this. – stanichou Jun 18 at 15:47
4

You can count the number of nodes in your legend using PyQGIS like below (standalone use)

project = QgsProject.instance()
manager = project.layoutManager()
layout = manager.layoutByName('myLayout')

# We consider only one legend
itemLegend = [item for item in layout.items() if isinstance(item, QgsLayoutItemLegend)][0]

# It's a tree, we should follow it recursively but did not
# want to bother: only took the first level for the example
layerTreeChildren = itemLegend.model().rootGroup().children()

# Count the number of elements in each legend block
[len(itemLegend.model().layerLegendNodes(i)) for i in layerTreeChildren]


particularLayer = next(iter(QgsProject.instance().mapLayersByName('my layer name')), None)

if particularLayer:
    # You could also use layerOriginalLegendNodes
    # instead of layerLegendNodes
    print(len(itemLegend.model().layerLegendNodes(itemLegend.model().rootGroup().findLayer(particularLayer))))

Then you will need to create a custom Python expression function to use it easily

@qgsfunction(args='auto', group='Custom', usesGeometry=False, referencedColumns=[])
def countLegendElements(layoutname, layername, feature, parent):
    project = QgsProject.instance()
    manager = project.layoutManager()
    layout = manager.layoutByName(layoutname)
    itemLegend = [item for item in layout.items() if isinstance(item, QgsLayoutItemLegend)][0]
    particularLayer = next(iter(QgsProject.instance().mapLayersByName(layername)), None)

    if particularLayer:
        # You could also use layerOriginalLegendNodes
        # instead of layerLegendNodes
        return len(itemLegend.model().layerLegendNodes(itemLegend.model().rootGroup().findLayer(particularLayer)))
    else:
        return None

Save the custom expression and use in the expression countLegendElements('layoutName', 'layerName') to get the count of legend nodes.

You can reuse this count to change conditions to set your columns number.

PS: the limitations of my current custom function are that I suppose there is only one legend in the composer (you could change the code to search with an id for the legend instead) and I also suppose your layer name is unique (if not, the code will take the first one). I also do not care particularly about highly nested layers.

| improve this answer | |
  • Is there a way to replace 'Parcs éoliens' in your code with a dynamic variable which works like @layout_name would for the expression builder? Also I do have 2 legends in my composer. I'm sorry to ask this, but what would the code look like if I were to search for a particular legend named "legend2"? When I use your code and expression as they are, the expression countLegendElements(@layout_name , 'test_rpg_17') returns NULL. I wish I could make it work on my own but I'm a bit overwhelmed when it comes to coding. – stanichou Jun 19 at 8:14
  • Made the fix for 'Parcs éoliens' (a copy and paste issue on my side). Copy again the @qgsfunction block, rerun it and execute again your countLegendElements(@layout_name , 'test_rpg_17'). It should work now. Tell me – ThomasG77 Jun 19 at 8:35
  • I'm sorry it turns out I used the name of the layout whereas I should have used the name of the layer. It works, thanks a lot. Now I do have 2 legends in my print composer. If I were to specify the name of the legend where the counting has to take place, could you tell me how it's done? I wish I could do it myself but I'm really not well versed in coding. – stanichou Jun 19 at 8:41
  • Set now def countLegendElements(layoutname, layername, legenditemid, feature, parent):, change itemLegend = ... line to itemLegend = layout.itemById(legenditemid). Rerun/reload the function again. Now call the function on a particular legend item with countLegendElements(@layout_name , 'test_rpg_17', 'yourownitemid') where yourownitemid is your Item ID e.g docs.qgis.org/3.10/en/docs/user_manual/print_composer/… PS: always told you to replace function but could also rename it and have the possibility to use both – ThomasG77 Jun 19 at 9:12

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.