Multiple colorbars changes the annotation fontsize

Hi

I’m plotting multiple colorbars side-by-side with different widths, and having trouble where the annotation size seems to scale with the colorbar width. I’ve done a lot of googling and a lot of trial and error and can’t seem to get the annotation sizes consistent. Below is an example:

import pygmt

pygmt.makecpt(cmap='batlow', series=[-50,40],background=True, output='cpt1.cpt')
cpt1 = 'cpt1.cpt'
cpt2 = pygmt.makecpt(cmap='polar', series=[-20,20], background=True,output='cpt2.cpt')
cpt2 = 'cpt2.cpt'

fig = pygmt.Figure()
pygmt.config(MAP_FRAME_TYPE="plain")
pygmt.config(FONT_ANNOT="8p")
with fig.subplot(
    nrows=1,
    ncols=4,
    figsize=(f"18c", f"12c"),
    sharex="b",
    sharey="l",
    frame=['WSne','xa120f60','ya60f60']
):
    fig.basemap(region='d', projection='Q?', panel=True)
    fig.colorbar(cmap=cpt1, position="jBL+o0c/-0.9c+w8.8c/0.25c+h", frame=["a20f5", "x+lTemperature"])

    fig.basemap(region='d', projection='Q?', panel=True)

    fig.basemap(region='d', projection='Q?', panel=True)
    fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.2c/0.25c+h", frame=["a10f2", "x+lDiff"])

    fig.basemap(region='d', projection='Q?', panel=True)
    fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.2c/0.25c+h", frame=["a10f2", "x+lDiff"])

fig.show()

And the result:

As you can see, the right two colorbars have smaller fonts than the left. Any idea how I can rectify this?

Thank you!

Hello @JonathonLeonard,

are you using PyGMT with GMT 6.5.0?

Up on GMT 6.5.0 the front sizes of labels and annotations of a colorbar are scaled relatively the the length of the colorbar. You can modify the GMT defaults locally to adjust the font size manually. For a general example please see How to set font size on scale bar? · Issue #3230 · GenericMappingTools/pygmt · GitHub.

Hi Yvonne,

I think GMT’s autoscaling is still applied on top of what is specified using with pygmt.config(...). Result is that fonts of a longer scalebar are always bigger than of a shorter one.

Please see some spaghetti code and plots below.

import pygmt

FONT_SIZES=['20p','10p','5p']

for FONT_SIZE in FONT_SIZES:
    FONT_ANNOT_PRIMARY=f'{FONT_SIZE},blue'
    FONT_ANNOT_SECONDARY=f'{FONT_SIZE},darkgreen'
    FONT_LABEL=f'{FONT_SIZE},orange'

    fig = pygmt.Figure()
    pygmt.config(MAP_FRAME_TYPE="plain")
    pygmt.makecpt(cmap='batlow', series=[-40,40],background=True)
    with fig.subplot(
        figsize=(f"18c", f"12c"),
        nrows=1, ncols=4,
        sharex="b", sharey="l",
        frame=['WSne','xa120f60','ya60f60']
    ):
        fig.basemap(region='d', projection='Q?', panel=True)

        with pygmt.config(
            FONT_ANNOT_PRIMARY=FONT_ANNOT_PRIMARY,
            FONT_ANNOT_SECONDARY=FONT_ANNOT_SECONDARY,
            FONT_LABEL=FONT_ANNOT_SECONDARY
        ): fig.colorbar(position="jBL+o0c/-0.9c+w215%/0.25c+h", frame=["a20f5", "x+lTemperature"])

        fig.basemap(region='d', projection='Q?', panel=True)

        fig.basemap(region='d', projection='Q?', panel=True)

        with pygmt.config(
            FONT_ANNOT_PRIMARY=FONT_ANNOT_PRIMARY,
            FONT_ANNOT_SECONDARY=FONT_ANNOT_SECONDARY,
            FONT_LABEL=FONT_ANNOT_SECONDARY
        ): fig.colorbar(position="jBL+o0c/-0.9c+w100%/0.25c+h", frame=["a10f2", "x+lDiff"])

        fig.basemap(region='d', projection='Q?', panel=True)
        
        with pygmt.config(
            FONT_ANNOT_PRIMARY=FONT_ANNOT_PRIMARY,
            FONT_ANNOT_SECONDARY=FONT_ANNOT_SECONDARY,
            FONT_LABEL=FONT_ANNOT_SECONDARY
        ): fig.colorbar(position="jBL+o0c/-0.9c+w100%/0.25c+h", frame=["a10f2", "x+lDiff"])

    fig.savefig(f'multiple_colorbars_side-by-side-FONT_{FONT_SIZE}.png',show=True)



Yes, autoscaling still applies if you manually adjust the font size. As mentioned in How to set font size on scale bar? · Issue #3230 · GenericMappingTools/pygmt · GitHub, it does not seem to be possible to set a fixed font size directly anymore (or I have not figured out how to do this now / in GMT 6.5.0).

I feel one has to set a smaller font size locally only for the longer scale bar in order to end up with the same font size as for the shorter scale bar (or vice versa).

import pygmt

cpt1 = 'cpt1.cpt'
pygmt.makecpt(cmap='batlow', series=[-50,40], background=True, output=cpt1)
cpt2 = 'cpt2.cpt'
pygmt.makecpt(cmap='polar', series=[-20,20], background=True, output=cpt2)


fig = pygmt.Figure()
pygmt.config(MAP_FRAME_TYPE="plain")
pygmt.config(FONT_ANNOT="8p")
with fig.subplot(
    nrows=1,
    ncols=4,
    figsize=(f"18c", f"12c"),
    sharex="b",
    sharey="l",
    frame=['WSne','xa120f60','ya60f60']
):
 
    fig.basemap(region='d', projection='Q?', panel=True)
    with pygmt.config(
        FONT_ANNOT_PRIMARY="6p",
        FONT_LABEL="10p",
    ): 
        fig.colorbar(cmap=cpt1, position="jBL+o0c/-0.9c+w8.8c/0.25c+h", frame=["a20f5", "x+lTemperature"])

    fig.basemap(region='d', projection='Q?', panel=True)

    fig.basemap(region='d', projection='Q?', panel=True)
    fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.2c/0.25c+h", frame=["a10f2", "x+lDiff"])

    fig.basemap(region='d', projection='Q?', panel=True)
    fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.2c/0.25c+h", frame=["a10f2", "x+lDiff"])

fig.show()

Thanks for the answer.

The same seems true for the color scales’ frame pen size, tick length/ pen size etc. The scalebars still look quite different even when annotation font visually looks the same.

If you set GMT_THEME=classic even in a modern mode session, you should be able to set exactly the font sizes (not tested).

Hm, yes, it seems like these parameters are also set relatively to the length of the colorbar in GMT 6.5.0.

Thanks @Joaquim for this suggestion!
I just tested this in a simple code example. However, it looks like that the auotscaling still applies.

import pygmt

size = 5
region = [-size, size, -size, size]
projection = "X10c/5c"
frame = 0

position_long = "jBC+w8c/0.3c+h"
position_short = "jTC+w3c/0.3c+h"
label_long = "x0.2+lshort colorbar"
label_short = "x0.2+llong colorbar"
cmap_long = "batlow"
cmap_short = "hawaii"


fig = pygmt.Figure()

# Upper panel
pygmt.config(GMT_THEME="classic")

fig.basemap(region=region, projection=projection, frame=frame)
fig.colorbar(position=position_short, frame=label_short, cmap=cmap_short)
fig.colorbar(position=position_long, frame=label_long, cmap=cmap_long)

fig.shift_origin(yshift="-5.5c")

# Lower panel
pygmt.config(GMT_THEME="classic", FONT_ANNOT_PRIMARY="10p", FONT_LABEL="12p")

fig.basemap(region=region, projection=projection, frame=frame)
fig.colorbar(position=position_short, frame=label_short, cmap=cmap_short)
fig.colorbar(position=position_long, frame=label_long, cmap=cmap_long)

fig.show()

So, in a modern session, it seems we can change the starting point (initial font sizes, tick …) but not the fact that it auto scales.

Thanks all for the help. So it seems the autoscaling is a feature/bug of gmt 6.5.0, and can’t be switched off. With inspiration from @yvonnefroehlich’s comment, I’ve manually made a figure that looks consistent by manually adjusting the font size, tick lengths and offsets (below).

I suppose another possible workaround would be to just work with gmt 6.4.0 for now. Other than that, I suppose I can put in a feature request in github to toggle the colorbar autoscaling on/off?

import pygmt

pygmt.makecpt(cmap='batlow', series=[-50,40],background=True, output='cpt1.cpt')
cpt1 = 'cpt1.cpt'
cpt2 = pygmt.makecpt(cmap='polar', series=[-20,20], background=True,output='cpt2.cpt')
cpt2 = 'cpt2.cpt'

fig = pygmt.Figure()
pygmt.config(MAP_FRAME_TYPE="plain")
pygmt.config(FONT_ANNOT="8p")
with fig.subplot(
    nrows=1,
    ncols=4,
    figsize=(f"18c", f"12c"),
    sharex="b",
    sharey="l",
    frame=['WSne','xa120f60','ya60f60']
):
    fig.basemap(region='d', projection='Q?', panel=True)
    with pygmt.config(
        FONT_ANNOT_PRIMARY="5.5p",
        FONT_LABEL="9.7p",
        MAP_TICK_LENGTH_PRIMARY="2.6p",
        MAP_TICK_LENGTH_SECONDARY="1.3p",
        MAP_ANNOT_OFFSET="1.94p",
    ): 
        fig.colorbar(cmap=cpt1, position="jBL+o0c/-0.9c+w8.8c/0.25c+h", frame=["a10f2", "x+lTemperature"])

    fig.basemap(region='d', projection='Q?', panel=True)

    fig.basemap(region='d', projection='Q?', panel=True)
    fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.2c/0.25c+h", frame=["a10f2", "x+lDiff"])

    fig.basemap(region='d', projection='Q?', panel=True)
    fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.2c/0.25c+h", frame=["a10f2", "x+lDiff"])

fig.show()

This looks like a GMT bug.

I don’t think it was ever the intention (likely for the lack of a good reason) to turn off the auto-scalling in the middle of a modern session. No idea on how hard is to change that.

Do you have an idea on the thinking behind auto-scaling colorbars? I feel like the use of multiple colorbars are a common use of GMT. Even if we use colorbar widths that are the same, it’s still desirable to have fontsizes consistent with other map elements. Should I be using classic mode instead for these type of situations?

Lots of food for reading here (and links).

PyGMT uses only modern mode.

Thanks for the directing me to those discussions. I see that the scaling factor is a square root of the cbar width/15. With that information, I’m able to apply the rescaling factor to the original code and have the colorbar in it’s true measurements. Code below (sorry it’s a bit messy):

import numpy as np
import pygmt

pygmt.makecpt(cmap='batlow', series=[-50,40],background=True, output='cpt1.cpt')
cpt1 = 'cpt1.cpt'
cpt2 = pygmt.makecpt(cmap='polar', series=[-20,20], background=True,output='cpt2.cpt')
cpt2 = 'cpt2.cpt'

def re_scale(cbar_len):
    return np.sqrt(cbar_len/15)

fig = pygmt.Figure()
pygmt.config(MAP_FRAME_TYPE="plain")
pygmt.config(FONT_ANNOT="8p")
with fig.subplot(
    nrows=1,
    ncols=4,
    figsize=(f"18c", f"12c"),
    sharex="b",
    sharey="l",
    frame=['WSne','xa120f60','ya60f60']
):
    fig.basemap(region='d', projection='Q?', panel=True)

    cbar_len = 8.8
    scaling = re_scale(cbar_len)
    with pygmt.config(
        FONT_ANNOT=f"{8/scaling}p",
        FONT_LABEL=f"{10/scaling}p",
        MAP_TICK_LENGTH_PRIMARY=f"{5/scaling}p",
        MAP_TICK_LENGTH_SECONDARY=f"{2.5/scaling}p",
        MAP_ANNOT_OFFSET=f"{5/scaling}p",
    ):    
        fig.colorbar(cmap=cpt1, position=f"jBL+o0c/-0.9c+w{cbar_len}c/0.25c+h", frame=["a10f2", "x+lTemperature"])

    fig.basemap(region='d', projection='Q?', panel=True)

    fig.basemap(region='d', projection='Q?', panel=True)
    cbar_len = 4.4
    scaling = re_scale(cbar_len)
    with pygmt.config(
        FONT_ANNOT=f"{8/scaling}p",
        FONT_LABEL=f"{10/scaling}p",
        MAP_TICK_LENGTH_PRIMARY=f"{5/scaling}p",
        MAP_TICK_LENGTH_SECONDARY=f"{2.5/scaling}p",
        MAP_ANNOT_OFFSET=f"{5/scaling}p",
    ): 
        fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.4c/0.25c+h", frame=["a10f2", "x+lDiff"])

    fig.basemap(region='d', projection='Q?', panel=True)
    with pygmt.config(
        FONT_ANNOT=f"{8/scaling}p",
        FONT_LABEL=f"{10/scaling}p",
        MAP_TICK_LENGTH_PRIMARY=f"{5/scaling}p",
        MAP_TICK_LENGTH_SECONDARY=f"{2.5/scaling}p",
        MAP_ANNOT_OFFSET=f"{5/scaling}p",
    ): 
        fig.colorbar(cmap=cpt2, position="jBL+o0c/-0.9c+w4.4c/0.25c+h", frame=["a10f2", "x+lDiff"])

fig.show()

Yep, @JonathonLeonard exactly :slightly_smiling_face:! Thanks for posting this.
I also thought about this kind of rescaling and I think it can serve as a workaround.
However, for new users or users bumping from GMT to 6.4.0 to 6.5.0 the autoscaling can be quite confusing (as seen in several forum posts and GitHub issues), especially because it still applies when a fixed value is given via the GMT defaults.

I agree, I would still argue that this is a feature that should be able somehow be ‘turned off’. This is particularly important when creating publication-quality figures, as many journals are quite particular on the fontsizes allowed for figures.

There is something called MAP_EMBELLISHMENT_MODE among configuration options in gmt.conf

  • MAP_EMBELLISHMENT_MODE

Determines if map embellishments like directional or magnetic compasses, map scales or vertical data scales should have attributes that scale with the size of the feature (auto) or use the settings as is (manual).

It however did not make any difference for me when I set it to manual. Color scale elements have still been autoscaled.