# Callout Labels with more lines

I have an issue with the Labeling in QGIS. I want to label lines, that are connected to each other, but have different attributes and so also different labels. The lines QGIS draws from the label to the line only marks one place of the line. In my case, this not very helpful because I can't see, where the start and the end of the line is.

I would like to have like two separate lines from the Label, which go to the start- and endpoint of the Line, so the visibility of the line segment, which is labeled gets improved.

The Labels should look like in the Picture above. This from AutoCAD.

• Easiest probably to create start- and end point of the lines. May 30, 2023 at 21:42
• Including a screenshot of the current situation and a mock-up of what you want to achieve would clarify your question immensely.
– Matt
May 31, 2023 at 6:07
• I Added a Picture to clarify.
– Toby
May 31, 2023 at 14:52

## 2 Answers

@Toby your question inspired me!

As @Tom already mentioned, we can solve the task with the Geometry Generator. Only in this case we need an additional Python expression function to solve the main part of the task: `determining the label boundary`.

I will explain the whole solution, but first let me show you the final result:

And here are the necessary steps:

1.) Add the Python code with the Function Editor of the Expression Dialog (tested with QGIS 3.30.1):

``````from qgis.core import *
from qgis.gui import *
from qgis.utils import iface
import math

def midpt(pt1, pt2):
x = (pt1.x() + pt2.x())/2
y = (pt1.y() + pt2.y())/2
return QgsPointXY(x,y)

def createRelPoint(point, dist, azim):
point_x, point_y = point.x(), point.y()
azim_rad = math.radians(azim)
x = point_x + dist * math.cos(azim_rad)
y = point_y + dist * math.sin(azim_rad)
return QgsPointXY(x, y)

@qgsfunction(args='auto', group='Custom', usesGeometry=True, referenced_columns=[])
def getLabelLeaders(x, y, handle_width, feature, parent):
center=QgsPointXY(x,y)
geom = feature.geometry().asPolyline()
startPt = geom[0]
endPt = geom[-1]
buffer=1
rec=QgsRectangle.fromCenterAndSize(center,buffer,buffer)
labels=iface.mapCanvas().labelingResults ().labelsWithinRect(rec)
#labels=iface.mapCanvas().labelingResults().labelsAtPosition(center)
if len(labels)>0:
polygon=labels[0].labelGeometry.asPolygon()[0]
p1 = midpt(polygon[0],polygon[3])
p2 = midpt(polygon[1],polygon[2])
if p1.distance(startPt) > p2.distance(startPt):
p1,p2 = p2,p1

azim = p1.azimuth(p2)
p1 = createRelPoint(p1,-handle_width,90-azim)
p2 = createRelPoint(p2,handle_width,90-azim)

l1 = QgsGeometry.fromPolylineXY([startPt,p1,center])
l2 = QgsGeometry.fromPolylineXY([endPt,p2,center])
mline = QgsGeometry.collectGeometry([l1,l2])
return mline

``````

2.) Create and configure your labels:

My example uses a `Single Label` value: ` "strasse" ||'\n'||"str_name"||'\nLänge: '|| round("measurelength",0)||'km'` with a rectangular background (all dimension values are in `Map Units` to prevent label resizing).

Move one label to activate the `Auxiliary Storage` mechanism. You can rotate your labels as well. After that open the label Placement tab and configure the label Alignment: `horizontal='Center', vertical='Half'`. Set Priority to High. On the Rendering tab I'm setting Overlapping Mode to `Allow Overlaps without Penalty`.

3.) Add the Geometry Generator symbol to your layer symbology:

`getLabelLeaders("auxiliary_storage_labeling_positionx","auxiliary_storage_labeling_positiony",300)`

The third parameter of the `getLabelLeaders` function defines the length of the short leader segments (I call it label handles).

Only labels that are moved will show the custom leaders.

The solution is not perfect, because sometimes label boundaries are not exact, but in most cases it should work as expected. If you don't see the leaders in your Print Layout, you have to zoom to the labels in Map Canvas and refresh the Layout afterwards (the expression needs Map Canvas to fetch the label boundaries).

BTW: it took me some hours to solve this task, but it was a lot of fun!

Enjoy, Christoph

• I even tried to solve the task with Callout Lines, but had an issue with the Geometry Generator (see github.com/qgis/QGIS/issues/53377). Jun 6, 2023 at 12:52
• Hi Christoph, sorry for my late response, but i was on holidays. I tried your solution today, and followed all the steps, but the lines dont show up in my project. Can it be an issue with the map canvas you mention at the end? I'm pretty new to qgis, and dont know anything about it.
– Toby
Jun 12, 2023 at 9:17
• Hi Toby, never mind. You need a projected coordinate system like Gauss Krüger. Jun 12, 2023 at 12:57
• Hi Christoph. I Just found the mistake. I Didn't delete the standart part in the Geometry generator. Now it works perfectly fine. Thanks for that nice work. If can appreciate your work in some way let me know.
– Toby
Jun 12, 2023 at 13:04
• @Toby maybe it's easier to create an AutoLisp Routine for labeling in AutoCAD/BricsCAD and export the generated labels to QGIS. Jun 20, 2023 at 15:35

You could use Rule-based Labeling, with Callouts and Geometry Generator to create the lines to the start and end points. I'm not convinced that the visibility is improved, but maybe it will for your data set. You may need to play around with the position of the labels.

1. Turn on Rule-based Labeling and create two rules

1. Enable Callouts

1. Use Geometry Generator to create the Start Point (`start_point(\$geometry)`). Similar for the other rule (End Point -> `end_point(\$geometry)`). Set the distance to some way away from the point eg 20 mm

Result on a small sample data set - a callout line to each end of the actual lines:

• Thanks for that quick answer, but this isn't what I was looking for. I need Something where the label is in the middle an the lines go from one label to start an endpoint. But Maybe I could play alittle with what you gave me. I'll try.
– Toby
May 31, 2023 at 14:44