I would like to label groups of points (clusters) with only one label but several call-out lines. I built on top of the very appealing answer from @eurojam here: Giving one label to two points using QGIS

In order to make a distance aware grouping, I used the following code under the geometry-generator creating a point geometry (in contrast to the polygon geometry in the original post):

centroid(collect($geometry, group_by:="Label", filter:= distance(
        $geometry, geometry(@parent)) <=  @map_scale/50

Now I omit duplicated labels (if two points are clustered, only one label is needed). This allows to make us of the built in label displacement engine (i.e. to avoid labels overlapping each other or certain features). Probably, someone more savvy might include this within the geometry-generator. However, I implemented the following code for a data driven override for show label under rendering, basically adapting the solution here: https://gis.stackexchange.com/a/396489/203521

array_first(array_agg($id, group_by:="Label", filter:= distance(
        $geometry, geometry(@parent)) <=  @map_scale/50)) = $id  

This code displays a single label per group of points within a scale dependent radius. If several points are close, but not very close, it creates multiple centroids, but only one label. Despite that artifact, it seems to be working quite good.

Now, I struggle displaying the call-out lines. They connect to the centroid of the points, the generated geometry.

I tried to tweak the data-defined origin and target-section in the callout section under labeling. So I could provide a couple of x- and y-coordinates, but this does not seem to be working. Only one line is shown. enter image description here

I know can create a line symbology with the geometry generator. The following expression works for labels I moved manually:

         group_by:="Label", filter:= distance(
                 $geometry, geometry(@parent)) <=  @map_scale/50)
         group_by:="Label", filter:= distance(
                 $geometry, geometry(@parent)) <=  @map_scale/50)

However I don't have the auxiliary variables by default and I don't know how to populate those fields other than moving the labels with the custom placement tool. So this works for labels I moved, but not for the ones placed by default (the green one I moved, the red and violet one I didn't).

enter image description here

I could create an additional layer with a geometry generator that creates a line between the points and the clusters centroid, as shown below. But that's not exactly what I intended.

enter image description here

Another Idea is to create a virtual layer. Something along the following code could be used, but scale dependent clustering would be another issue then.

select "Label",st_collect(geometry) as geometry
from Singlepart
Group by "Label"

But the result is not exactly what I expected. One label per point (not per multipoint) and callout lines scale dependent to some selection features (as visible in the labels of group 1) enter image description here enter image description here

Any other ideas?

Similar questions I found:

1 Answer 1


Create a multi-point layer:

enter image description here

Then, in this order:

  1. define your label style,
  2. move the labels
  3. select Draw lines to all feature parts:

enter image description here


enter image description here

This is somewhat trick, as it seems not all multi-parts are handled correctly. In my example, initially the multi-points labeled "P" showed the correct multi-callout, the ones labeled "E" did not... A refresh helped.

  • 1
    Many thanks! I thought this might be a solution by creating a virtual layer and apply the labeling there. But I didn't succeed. I applied select "Label",st_collect(geometry) as geometry from Singlepart Group by "Label" but it creates a label per feature and not one per entry. The callout lines then are to some elements. "Label all elements of a multipart feature" is unchecked. I might add some edit.
    – Beni
    Commented Nov 21, 2023 at 8:51

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.