3

I managed to have a decent result and I am seeking a better way to do it. Please see the image below.

So far, to achieve the result in the image, I have tried:

Rule-based labeling: It's basically two labels, one for each different fonts: Works ok but difficult to keep the two labels at same relative distance when manually placing them and difficult to have it look like one call-out.

Annotations: Best option for me but I cannot edit the "bubble" color or style. I did not find a way to fill the annotation with a layer field. I seem to have to fill each annotations by hand.

The ideal solution I can think of would be a regular single label in witch I could control text format per line.

The image I added show what I did with annotations and it is exactly the result I am looking for: Annotations that I can place by hand and have different text styles within it. I'm just looking for a better and faster way to do it and I would also like to be able to format the call-outs by changing background color and have round corners and change the callout line style too.

Annotation example

1 Answer 1

3

There is no simple way of doing this, but a possibility is to use the idea from the accepted answer for QGIS Labels with HTML formating.

You need to set the value as '' (an empty string). The size should be set to some fixed value. This interplays with how the size of the labels are defined and may need some tweaking.

enter image description here

(The answer I linked to used rule based labeling, in this case, the label is always the same, so one can just as well use simple labels, unless one need to use different types of labels)

I made a test layer with two fields: Header and Text. For the testing, I used '#' as the line separators, '\n' or other sensible line separators will probably work just fine. The difficult thing is then to make the multiple lines for your text. To just make a white square with the text in, I made the following expression:

'data:image/svg+xml;utf8,<svg width="600" height="300" version="1.1"
 viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
 <g fill="#FFFFFF" fill-opacity=".7" stroke="#000000">
    <rect width="100" height="50" />
 </g>
 <g alignment-baseline="middle" stroke-width=".25" text-anchor="middle">
 <text text-anchor="start" y="15" font-size="15px">'||"Header"||'</text>'
 ||multilineSVGtext("Langtekst",'#',20,10,10)||'
 </g>
 </svg>'

To split the text into lines, I wrote the following function:

from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom', referenced_columns=[])

def multilineSVGtext(intext, separator, start, size, x, feature, parent):
  svgfrag = ''
  lines = intext.split(separator)
  y=start+size*1.2
  for line in lines:
    svgfrag += f'<text x="{x}"  y="{y}" font-size="{size}" text-anchor="start">{line}</text>'
    y += size*1.2
  return svgfrag

This gave me the following labels:

enter image description here

This simple labels does not work properly if there are large differences in the amount of text in the labels. To have a dynamic size, I wrote the following function:

from qgis.core import *
from qgis.gui import *
import math


@qgsfunction(args='auto', group='Custom', referenced_columns=[])

def svglabel(header,labeltext,feature,parent):
    textsize = 10
    start = 20
    lines = labeltext.split('#') # Has used # as a line marker in the field. Other things probably works fine
    height = start + textsize*1.2*len(lines)+textsize
    length = len(max(lines, key=len)) 
    width = math.ceil(length * 0.7 * textsize) # estimate of average width / height in a proportional font
    # Width for the svg must be set to a fixed value or qgis will scale the labels differently to force the same width
    svg=f'''data:image/svg+xml;utf8,<svg width="150" height="{height}" version="1.1" viewBox="0 0 150 {height}" xmlns="http://www.w3.org/2000/svg">
        <g fill="#FFFFFF" fill-opacity=".7" stroke="#000000">
           <rect width="{width}" height="{height}" />
        </g>
        <g alignment-baseline="middle" stroke-width=".25" text-anchor="start">
        <text y="15" font-size="15px">{header}</text>
        '''
    y=start+textsize*1.2
    x=10
    for line in lines:
        svg +=  f'<text x="{x}"  y="{y}" font-size="{textsize}">{line}</text>'
        y += textsize*1.2
    svg += '</g></svg>'
    # print(svg) # This will print the svg to the python console for debugging
    return svg

this function is the called in the expression with

svglabel(headerfield,textfield)

This gives the following result with the labels set to fixed size 45mm. (The points are the orange dots behind the labels) enter image description here

As can be seen, there are some problems with overlapping labels. For this case it works, but it is also here needed to play around a bit with the various labeling parameters.

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.