From b5859690960e966d4883f60b12b5a7896cb276d8 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 26 Jul 2025 15:22:43 +0800 Subject: [PATCH 01/16] Add Figure.scalebar to plot a scale bar on maps --- doc/api/index.rst | 1 + pygmt/figure.py | 1 + pygmt/src/__init__.py | 1 + pygmt/src/scalebar.py | 163 ++++++++++++++++++ pygmt/tests/baseline/test_scalebar.png.dvc | 5 + .../baseline/test_scalebar_cartesian.png.dvc | 5 + .../baseline/test_scalebar_complete.png.dvc | 5 + pygmt/tests/test_scalebar.py | 62 +++++++ 8 files changed, 243 insertions(+) create mode 100644 pygmt/src/scalebar.py create mode 100644 pygmt/tests/baseline/test_scalebar.png.dvc create mode 100644 pygmt/tests/baseline/test_scalebar_cartesian.png.dvc create mode 100644 pygmt/tests/baseline/test_scalebar_complete.png.dvc create mode 100644 pygmt/tests/test_scalebar.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 264f5a9175a..9b312ca2f9f 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -31,6 +31,7 @@ Plotting map elements Figure.inset Figure.legend Figure.logo + Figure.scalebar Figure.solar Figure.text Figure.timestamp diff --git a/pygmt/figure.py b/pygmt/figure.py index 56ad2c3d5cf..3c48080c482 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -427,6 +427,7 @@ def _repr_html_(self) -> str: plot3d, psconvert, rose, + scalebar, set_panel, shift_origin, solar, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 8905124f917..2aa4e6b5587 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -43,6 +43,7 @@ from pygmt.src.project import project from pygmt.src.psconvert import psconvert from pygmt.src.rose import rose +from pygmt.src.scalebar import scalebar from pygmt.src.select import select from pygmt.src.shift_origin import shift_origin from pygmt.src.solar import solar diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py new file mode 100644 index 00000000000..131346a125a --- /dev/null +++ b/pygmt/src/scalebar.py @@ -0,0 +1,163 @@ +""" +scalebar - Add a scale bar. +""" + +from collections.abc import Sequence +from typing import Literal + +from pygmt._typing import AnchorCode +from pygmt.alias import Alias, AliasSystem +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import build_arg_list, fmt_docstring +from pygmt.params import Box, Position +from pygmt.src._common import _parse_position + +__doctest_skip__ = ["scalebar"] + + +@fmt_docstring +def scalebar( # noqa: PLR0913 + self, + position: Position | Sequence[float | str] | AnchorCode | None = None, + length: float | str | None = None, + height: float | str | None = None, + scale_position: float | Sequence[float] | bool = False, + label: str | bool = False, + label_alignment: Literal["left", "right", "top", "bottom"] | None = None, + unit: bool = False, + fancy: bool = False, + vertical: bool = False, + box: Box | bool = False, + verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] + | bool = False, + panel: int | Sequence[int] | bool = False, + perspective: float | Sequence[float] | str | bool = False, + transparency: float | None = None, +): + """ + Add a scale bar on the plot. + + Parameters + ---------- + position + Position of the scale bar on the plot. It can be specified in multiple ways: + + - A :class:`pygmt.params.Position` object to fully control the reference point, + anchor point, and offset. + - A sequence of two values representing the x and y coordinates in plot + coordinates, e.g., ``(1, 2)`` or ``("1c", "2c")``. + - A :doc:`2-character justification code ` for a + position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot. + + If not specified, defaults to the bottom-left corner of the plot with a 0.2-cm + and 0.4-cm offset in the x- and y-directions, respectively. + length + Length of the scale bar in km. Append a suffix to specify different units. Valid + units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute mile; + **n**: nautical miles; **u**: US Survey foot. + height + Height of the scale bar. Only works when ``fancy=True``. [Default is ``"5p"``]. + scale_position + Specify the location where on a geographic map the scale applies. It can be: + + - *slat*: Map scale is calculated for latitude *slat* + - (*slon*, *slat*): Map scale is calculated for latitude *slat* and longitude + *slon*, which is useful for oblique projections. + - ``True``: Map scale is calculated for the middle of the map. + - ``False``: Default to the location of the reference point. + label + Text string to use as the scale bar label. If ``False``, no label is drawn. If + ``True``, the distance unit provided in the ``length`` parameter (default is km) + is used as the label. This parameter requires ``fancy=True``. + label_alignment + Alignment of the scale bar label. Choose from ``"left"``, ``"right"``, + ``"top"``, or ``"bottom"``. [Default is ``"top"``]. + fancy + If ``True``, draw a "fancy" scale bar, which is a segmented bar with alternating + black and white rectangles. If ``False``, draw a plain scale bar. + unit + If ``True``, append the unit to all distance annotations along the scale. For a + plain scale, this will instead select the unit to be appended to the distance + length. The unit is determined from the suffix in the ``length`` or defaults to + ``"km"``. + vertical + If ``True``, plot a vertical rather than a horizontal Cartesian scale. + box + Draw a background box behind the directional rose. If set to ``True``, a simple + rectangular box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box + appearance, pass a :class:`pygmt.params.Box` object to control style, fill, pen, + and other box properties. + $verbose + $panel + $perspective + $transparency + + Examples + -------- + >>> import pygmt + >>> from pygmt.params import Box, Position + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[0, 80, -30, 30], projection="M10c", frame=True) + >>> fig.scalebar( + ... position=Position((10, 10), cstype="mapcoords"), + ... length=1000, + ... fancy=True, + ... label="Scale", + ... unit=True, + ... ) + >>> fig.show() + """ + self._activate_figure() + + position = _parse_position( + position, + kwdict={ + "length": length, + "height": height, + "label_alignment": label_alignment, + "scale_position": scale_position, + "fancy": fancy, + "label": label, + "unit": unit, + "vertical": vertical, + }, + default=Position("BL", offset=(0.2, 0.4)), # Default to "BL" with offset. + ) + + if length is None: + msg = "Parameter 'length' must be specified." + raise GMTInvalidInput(msg) + + aliasdict = AliasSystem( + F=Alias(box, name="box"), + L=[ + Alias(position, name="position"), + Alias(length, name="length", prefix="+w"), + Alias( + label_alignment, + name="label_alignment", + prefix="+a", + mapping={"left": "l", "right": "r", "top": "t", "bottom": "b"}, + ), + Alias(scale_position, name="scale_position", prefix="+c", sep="/", size=2), + Alias(fancy, name="fancy", prefix="+f"), + Alias(label, name="label", prefix="+l"), + Alias(unit, name="unit", prefix="+u"), + Alias(vertical, name="vertical", prefix="+v"), + ], + ).add_common( + V=verbose, + c=panel, + p=perspective, + t=transparency, + ) + + confdict = {} + if height is not None: + confdict["MAP_SCALE_HEIGHT"] = height + + with Session() as lib: + lib.call_module( + module="basemap", args=build_arg_list(aliasdict, confdict=confdict) + ) diff --git a/pygmt/tests/baseline/test_scalebar.png.dvc b/pygmt/tests/baseline/test_scalebar.png.dvc new file mode 100644 index 00000000000..b89d76011e6 --- /dev/null +++ b/pygmt/tests/baseline/test_scalebar.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: ad7658ed25f1a9f0a1ba74a0ffa84a4b + size: 10207 + hash: md5 + path: test_scalebar.png diff --git a/pygmt/tests/baseline/test_scalebar_cartesian.png.dvc b/pygmt/tests/baseline/test_scalebar_cartesian.png.dvc new file mode 100644 index 00000000000..0d01ea6cca5 --- /dev/null +++ b/pygmt/tests/baseline/test_scalebar_cartesian.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: e09a7c67f6146530ea594694853b6f98 + size: 6508 + hash: md5 + path: test_scalebar_cartesian.png diff --git a/pygmt/tests/baseline/test_scalebar_complete.png.dvc b/pygmt/tests/baseline/test_scalebar_complete.png.dvc new file mode 100644 index 00000000000..4ac6b71bb99 --- /dev/null +++ b/pygmt/tests/baseline/test_scalebar_complete.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: c018b219d3ebc719fb1b1686e074dcd9 + size: 11749 + hash: md5 + path: test_scalebar_complete.png diff --git a/pygmt/tests/test_scalebar.py b/pygmt/tests/test_scalebar.py new file mode 100644 index 00000000000..eff1c44aeb7 --- /dev/null +++ b/pygmt/tests/test_scalebar.py @@ -0,0 +1,62 @@ +""" +Test Figure.scalebar. +""" + +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput +from pygmt.params import Position + + +@pytest.mark.mpl_image_compare +def test_scalebar(): + """ + Create a map with a scale bar. + """ + fig = Figure() + fig.basemap(region=[100, 120, 20, 30], projection="M10c", frame=True) + fig.scalebar(length=500) + return fig + + +@pytest.mark.mpl_image_compare +def test_scalebar_complete(): + """ + Test all parameters of scalebar. + """ + fig = Figure() + fig.basemap(region=[100, 120, 20, 30], projection="M10c", frame=True) + fig.scalebar( + position=Position((110, 22), cstype="mapcoords"), + length=1000, + height="10p", + fancy=True, + label="Scale", + label_alignment="left", + scale_position=(110, 25), + unit=True, + box=True, + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_scalebar_cartesian(): + """ + Test scale bar in Cartesian coordinates. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 5], projection="X10c/5c", frame=True) + fig.scalebar(position=Position((2, 1), cstype="mapcoords"), length=1) + fig.scalebar(position=Position((4, 1), cstype="mapcoords"), length=1, vertical=True) + return fig + + +def test_scalebar_no_length(): + """ + Test that an error is raised when length is not provided. + """ + fig = Figure() + fig.basemap(region=[100, 120, 20, 30], projection="M10c", frame=True) + with pytest.raises(GMTInvalidInput): + fig.scalebar(position=Position((118, 22), cstype="mapcoords")) From dc74f82afbfe9f214664a0ea474339af1edbed9b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 20 Dec 2025 09:15:44 +0800 Subject: [PATCH 02/16] Fix typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 131346a125a..e7410416f6a 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -45,7 +45,7 @@ def scalebar( # noqa: PLR0913 - A :class:`pygmt.params.Position` object to fully control the reference point, anchor point, and offset. - - A sequence of two values representing the x and y coordinates in plot + - A sequence of two values representing the x- and y-coordinates in plot coordinates, e.g., ``(1, 2)`` or ``("1c", "2c")``. - A :doc:`2-character justification code ` for a position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot. From 3c56bca08eeaf78fcfafc5ef6d8939c244d41146 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 20 Dec 2025 09:16:03 +0800 Subject: [PATCH 03/16] Fix tyops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index e7410416f6a..09ffe5170cf 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -50,7 +50,7 @@ def scalebar( # noqa: PLR0913 - A :doc:`2-character justification code ` for a position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot. - If not specified, defaults to the bottom-left corner of the plot with a 0.2-cm + If not specified, defaults to the Bottom Left corner of the plot with a 0.2-cm and 0.4-cm offset in the x- and y-directions, respectively. length Length of the scale bar in km. Append a suffix to specify different units. Valid From 2bb298b6df355bf59f915898fb4732d087630465 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 27 Dec 2025 21:20:20 +0800 Subject: [PATCH 04/16] Rename parameter scale_position to scale_at --- pygmt/src/scalebar.py | 12 ++++++------ pygmt/tests/test_scalebar.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 09ffe5170cf..fec4ed56353 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -22,7 +22,7 @@ def scalebar( # noqa: PLR0913 position: Position | Sequence[float | str] | AnchorCode | None = None, length: float | str | None = None, height: float | str | None = None, - scale_position: float | Sequence[float] | bool = False, + scale_at: float | Sequence[float] | bool = False, label: str | bool = False, label_alignment: Literal["left", "right", "top", "bottom"] | None = None, unit: bool = False, @@ -58,10 +58,10 @@ def scalebar( # noqa: PLR0913 **n**: nautical miles; **u**: US Survey foot. height Height of the scale bar. Only works when ``fancy=True``. [Default is ``"5p"``]. - scale_position - Specify the location where on a geographic map the scale applies. It can be: + scale_at + Specify the location where the map scale is calculated. It can be: - - *slat*: Map scale is calculated for latitude *slat* + - *slat*: Map scale is calculated for latitude *slat*. - (*slon*, *slat*): Map scale is calculated for latitude *slat* and longitude *slon*, which is useful for oblique projections. - ``True``: Map scale is calculated for the middle of the map. @@ -116,7 +116,7 @@ def scalebar( # noqa: PLR0913 "length": length, "height": height, "label_alignment": label_alignment, - "scale_position": scale_position, + "scale_at": scale_at, "fancy": fancy, "label": label, "unit": unit, @@ -140,7 +140,7 @@ def scalebar( # noqa: PLR0913 prefix="+a", mapping={"left": "l", "right": "r", "top": "t", "bottom": "b"}, ), - Alias(scale_position, name="scale_position", prefix="+c", sep="/", size=2), + Alias(scale_at, name="scale_at", prefix="+c", sep="/", size=2), Alias(fancy, name="fancy", prefix="+f"), Alias(label, name="label", prefix="+l"), Alias(unit, name="unit", prefix="+u"), diff --git a/pygmt/tests/test_scalebar.py b/pygmt/tests/test_scalebar.py index eff1c44aeb7..79ea6b3753e 100644 --- a/pygmt/tests/test_scalebar.py +++ b/pygmt/tests/test_scalebar.py @@ -33,7 +33,7 @@ def test_scalebar_complete(): fancy=True, label="Scale", label_alignment="left", - scale_position=(110, 25), + scale_at=(110, 25), unit=True, box=True, ) From 5a31c6cde1e30295652c3f70ee53c47b61b376d3 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 29 Dec 2025 09:06:44 +0800 Subject: [PATCH 05/16] Update pygmt/src/scalebar.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index fec4ed56353..7f257fcaf46 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -96,7 +96,7 @@ def scalebar( # noqa: PLR0913 Examples -------- >>> import pygmt - >>> from pygmt.params import Box, Position + >>> from pygmt.params import Position >>> fig = pygmt.Figure() >>> fig.basemap(region=[0, 80, -30, 30], projection="M10c", frame=True) >>> fig.scalebar( From 78a93071e1c912d2bec9bb66a5cfd463fbdb3dee Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 11 Jan 2026 22:31:27 +0800 Subject: [PATCH 06/16] Length is a required parameter --- pygmt/src/scalebar.py | 35 +++++++++++------------------------ pygmt/tests/test_scalebar.py | 17 +++-------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 7f257fcaf46..3f5a49e9266 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -8,7 +8,6 @@ from pygmt._typing import AnchorCode from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import build_arg_list, fmt_docstring from pygmt.params import Box, Position from pygmt.src._common import _parse_position @@ -19,9 +18,9 @@ @fmt_docstring def scalebar( # noqa: PLR0913 self, - position: Position | Sequence[float | str] | AnchorCode | None = None, - length: float | str | None = None, + length: float | str, height: float | str | None = None, + position: Position | Sequence[float | str] | AnchorCode | None = None, scale_at: float | Sequence[float] | bool = False, label: str | bool = False, label_alignment: Literal["left", "right", "top", "bottom"] | None = None, @@ -40,6 +39,12 @@ def scalebar( # noqa: PLR0913 Parameters ---------- + length + Length of the scale bar in km. Append a suffix to specify different units. Valid + units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute mile; + **n**: nautical miles; **u**: US Survey foot. + height + Height of the scale bar. Only works when ``fancy=True``. [Default is ``"5p"``]. position Position of the scale bar on the plot. It can be specified in multiple ways: @@ -52,12 +57,6 @@ def scalebar( # noqa: PLR0913 If not specified, defaults to the Bottom Left corner of the plot with a 0.2-cm and 0.4-cm offset in the x- and y-directions, respectively. - length - Length of the scale bar in km. Append a suffix to specify different units. Valid - units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute mile; - **n**: nautical miles; **u**: US Survey foot. - height - Height of the scale bar. Only works when ``fancy=True``. [Default is ``"5p"``]. scale_at Specify the location where the map scale is calculated. It can be: @@ -100,8 +99,8 @@ def scalebar( # noqa: PLR0913 >>> fig = pygmt.Figure() >>> fig.basemap(region=[0, 80, -30, 30], projection="M10c", frame=True) >>> fig.scalebar( - ... position=Position((10, 10), cstype="mapcoords"), ... length=1000, + ... position=Position((10, 10), cstype="mapcoords"), ... fancy=True, ... label="Scale", ... unit=True, @@ -110,25 +109,13 @@ def scalebar( # noqa: PLR0913 """ self._activate_figure() + # Parse the 'position' parameter. + # No need to check conflicts with other parameters since it's a new function. position = _parse_position( position, - kwdict={ - "length": length, - "height": height, - "label_alignment": label_alignment, - "scale_at": scale_at, - "fancy": fancy, - "label": label, - "unit": unit, - "vertical": vertical, - }, default=Position("BL", offset=(0.2, 0.4)), # Default to "BL" with offset. ) - if length is None: - msg = "Parameter 'length' must be specified." - raise GMTInvalidInput(msg) - aliasdict = AliasSystem( F=Alias(box, name="box"), L=[ diff --git a/pygmt/tests/test_scalebar.py b/pygmt/tests/test_scalebar.py index 79ea6b3753e..9379885d305 100644 --- a/pygmt/tests/test_scalebar.py +++ b/pygmt/tests/test_scalebar.py @@ -4,7 +4,6 @@ import pytest from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput from pygmt.params import Position @@ -27,9 +26,9 @@ def test_scalebar_complete(): fig = Figure() fig.basemap(region=[100, 120, 20, 30], projection="M10c", frame=True) fig.scalebar( - position=Position((110, 22), cstype="mapcoords"), length=1000, height="10p", + position=Position((110, 22), cstype="mapcoords"), fancy=True, label="Scale", label_alignment="left", @@ -47,16 +46,6 @@ def test_scalebar_cartesian(): """ fig = Figure() fig.basemap(region=[0, 10, 0, 5], projection="X10c/5c", frame=True) - fig.scalebar(position=Position((2, 1), cstype="mapcoords"), length=1) - fig.scalebar(position=Position((4, 1), cstype="mapcoords"), length=1, vertical=True) + fig.scalebar(length=1, position=Position((2, 1), cstype="mapcoords")) + fig.scalebar(length=1, position=Position((4, 1), cstype="mapcoords"), vertical=True) return fig - - -def test_scalebar_no_length(): - """ - Test that an error is raised when length is not provided. - """ - fig = Figure() - fig.basemap(region=[100, 120, 20, 30], projection="M10c", frame=True) - with pytest.raises(GMTInvalidInput): - fig.scalebar(position=Position((118, 22), cstype="mapcoords")) From 7988be102f432f963d0fb748bde014b7168401fb Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 11 Jan 2026 23:34:27 +0800 Subject: [PATCH 07/16] Need to pass kwdict --- pygmt/src/scalebar.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 3f5a49e9266..2b941f12c7f 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -108,11 +108,9 @@ def scalebar( # noqa: PLR0913 >>> fig.show() """ self._activate_figure() - - # Parse the 'position' parameter. - # No need to check conflicts with other parameters since it's a new function. position = _parse_position( position, + kwdict={}, # No need to check conflicts since it's a new function. default=Position("BL", offset=(0.2, 0.4)), # Default to "BL" with offset. ) From 1c53522f4afd506861dcb7ef662c16677bdf205b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 12 Jan 2026 13:10:52 +0800 Subject: [PATCH 08/16] Fix a typo [skip ci] Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pygmt/src/scalebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 2b941f12c7f..fe591818189 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -83,7 +83,7 @@ def scalebar( # noqa: PLR0913 vertical If ``True``, plot a vertical rather than a horizontal Cartesian scale. box - Draw a background box behind the directional rose. If set to ``True``, a simple + Draw a background box behind the scale bar. If set to ``True``, a simple rectangular box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box appearance, pass a :class:`pygmt.params.Box` object to control style, fill, pen, and other box properties. From c192853ae0a9d090f6d0f8d65d1ffcd43807e288 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 17 Jan 2026 15:07:38 +0800 Subject: [PATCH 09/16] Update pygmt/src/scalebar.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pygmt/src/scalebar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index fe591818189..d07da1d1f6d 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -41,8 +41,8 @@ def scalebar( # noqa: PLR0913 ---------- length Length of the scale bar in km. Append a suffix to specify different units. Valid - units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute mile; - **n**: nautical miles; **u**: US Survey foot. + units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute miles; + **n**: nautical miles; **u**: US survey feet. height Height of the scale bar. Only works when ``fancy=True``. [Default is ``"5p"``]. position From 777cd3a95190d9432e2e3e1d716e8e6e314ab2a3 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 17 Jan 2026 21:54:41 +0800 Subject: [PATCH 10/16] Fix docstrings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index d07da1d1f6d..74e6b8a5f89 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -40,7 +40,7 @@ def scalebar( # noqa: PLR0913 Parameters ---------- length - Length of the scale bar in km. Append a suffix to specify different units. Valid + Length of the scale bar in kilometers. Append a suffix to specify another unit. Valid units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute miles; **n**: nautical miles; **u**: US survey feet. height From 188367a1fee6d47a5771fecdbe1fe609ce127b23 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 17 Jan 2026 22:33:46 +0800 Subject: [PATCH 11/16] Improve docstrings for fancy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 74e6b8a5f89..17c4cc4ffbb 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -74,7 +74,8 @@ def scalebar( # noqa: PLR0913 ``"top"``, or ``"bottom"``. [Default is ``"top"``]. fancy If ``True``, draw a "fancy" scale bar, which is a segmented bar with alternating - black and white rectangles. If ``False``, draw a plain scale bar. + black and white rectangles. If ``False``, draw a plain scale bar. Only supported + for non-Cartesian projections. unit If ``True``, append the unit to all distance annotations along the scale. For a plain scale, this will instead select the unit to be appended to the distance From c7b86c902954179e907be709ad800a75f92c41bd Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 18 Jan 2026 00:36:20 +0800 Subject: [PATCH 12/16] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 17c4cc4ffbb..9c79b1d0855 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -44,7 +44,7 @@ def scalebar( # noqa: PLR0913 units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute miles; **n**: nautical miles; **u**: US survey feet. height - Height of the scale bar. Only works when ``fancy=True``. [Default is ``"5p"``]. + Height of the scale bar [Default is ``"5p"``]. Only works when ``fancy=True``. position Position of the scale bar on the plot. It can be specified in multiple ways: @@ -66,12 +66,12 @@ def scalebar( # noqa: PLR0913 - ``True``: Map scale is calculated for the middle of the map. - ``False``: Default to the location of the reference point. label - Text string to use as the scale bar label. If ``False``, no label is drawn. If - ``True``, the distance unit provided in the ``length`` parameter (default is km) - is used as the label. This parameter requires ``fancy=True``. + Text string for the scale bar label. If ``False``, no label is added. If + ``True``, the distance unit provided in the ``length`` parameter is used + [Default is ``"km"``]. Requires ``fancy=True``. label_alignment Alignment of the scale bar label. Choose from ``"left"``, ``"right"``, - ``"top"``, or ``"bottom"``. [Default is ``"top"``]. + ``"top"``, or ``"bottom"`` [Default is ``"top"``]. fancy If ``True``, draw a "fancy" scale bar, which is a segmented bar with alternating black and white rectangles. If ``False``, draw a plain scale bar. Only supported @@ -79,8 +79,8 @@ def scalebar( # noqa: PLR0913 unit If ``True``, append the unit to all distance annotations along the scale. For a plain scale, this will instead select the unit to be appended to the distance - length. The unit is determined from the suffix in the ``length`` or defaults to - ``"km"``. + length. The unit is determined from the suffix provided to the ``length`` + parameter or defaults to ``"km"``. vertical If ``True``, plot a vertical rather than a horizontal Cartesian scale. box From cc15a315da5a4c11d043af25ba07a5c8dd0edbbb Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 18 Jan 2026 10:09:31 +0800 Subject: [PATCH 13/16] Update pygmt/src/scalebar.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 9c79b1d0855..b5866ef85ac 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -40,9 +40,9 @@ def scalebar( # noqa: PLR0913 Parameters ---------- length - Length of the scale bar in kilometers. Append a suffix to specify another unit. Valid - units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute miles; - **n**: nautical miles; **u**: US survey feet. + Length of the scale bar in kilometers. Append a suffix to specify another unit. + Valid units are: **e**: meters; **f**: feet; **k**: kilometers; **M**: statute + miles; **n**: nautical miles; **u**: US survey feet. height Height of the scale bar [Default is ``"5p"``]. Only works when ``fancy=True``. position From 971d147948d54c3bb525349352f22b4e3a21c02f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 19 Jan 2026 10:37:09 +0800 Subject: [PATCH 14/16] Simplify _parse_position --- pygmt/src/scalebar.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index b5866ef85ac..09fd516a5d2 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -109,11 +109,7 @@ def scalebar( # noqa: PLR0913 >>> fig.show() """ self._activate_figure() - position = _parse_position( - position, - kwdict={}, # No need to check conflicts since it's a new function. - default=Position("BL", offset=(0.2, 0.4)), # Default to "BL" with offset. - ) + position = _parse_position(position, default=Position("BL", offset=(0.2, 0.4))) aliasdict = AliasSystem( F=Alias(box, name="box"), From 0561ba42f610431ec8f907e27f7a2c6a972215a1 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 20 Jan 2026 09:17:44 +0800 Subject: [PATCH 15/16] Rename scale_at to scale_loc --- pygmt/src/scalebar.py | 6 +++--- pygmt/tests/test_scalebar.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 09fd516a5d2..78972c9f6c1 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -21,7 +21,7 @@ def scalebar( # noqa: PLR0913 length: float | str, height: float | str | None = None, position: Position | Sequence[float | str] | AnchorCode | None = None, - scale_at: float | Sequence[float] | bool = False, + scale_loc: float | Sequence[float] | bool = False, label: str | bool = False, label_alignment: Literal["left", "right", "top", "bottom"] | None = None, unit: bool = False, @@ -57,7 +57,7 @@ def scalebar( # noqa: PLR0913 If not specified, defaults to the Bottom Left corner of the plot with a 0.2-cm and 0.4-cm offset in the x- and y-directions, respectively. - scale_at + scale_loc Specify the location where the map scale is calculated. It can be: - *slat*: Map scale is calculated for latitude *slat*. @@ -122,7 +122,7 @@ def scalebar( # noqa: PLR0913 prefix="+a", mapping={"left": "l", "right": "r", "top": "t", "bottom": "b"}, ), - Alias(scale_at, name="scale_at", prefix="+c", sep="/", size=2), + Alias(scale_loc, name="scale_loc", prefix="+c", sep="/", size=2), Alias(fancy, name="fancy", prefix="+f"), Alias(label, name="label", prefix="+l"), Alias(unit, name="unit", prefix="+u"), diff --git a/pygmt/tests/test_scalebar.py b/pygmt/tests/test_scalebar.py index 9379885d305..176adcf34bb 100644 --- a/pygmt/tests/test_scalebar.py +++ b/pygmt/tests/test_scalebar.py @@ -32,7 +32,7 @@ def test_scalebar_complete(): fancy=True, label="Scale", label_alignment="left", - scale_at=(110, 25), + scale_loc=(110, 25), unit=True, box=True, ) From cc40c8fa8cc1e2a54f53f70b6d5457cc4f47a12a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 21 Jan 2026 08:52:51 +0800 Subject: [PATCH 16/16] Update pygmt/src/scalebar.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/scalebar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/scalebar.py b/pygmt/src/scalebar.py index 78972c9f6c1..918d10bbcd5 100644 --- a/pygmt/src/scalebar.py +++ b/pygmt/src/scalebar.py @@ -82,7 +82,8 @@ def scalebar( # noqa: PLR0913 length. The unit is determined from the suffix provided to the ``length`` parameter or defaults to ``"km"``. vertical - If ``True``, plot a vertical rather than a horizontal Cartesian scale. + If ``True``, plot a vertical rather than a horizontal scale. Only + supported for Cartesian projections. box Draw a background box behind the scale bar. If set to ``True``, a simple rectangular box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box