Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Plotting map elements
Figure.inset
Figure.legend
Figure.logo
Figure.scalebar
Figure.solar
Figure.text
Figure.timestamp
Expand Down
1 change: 1 addition & 0 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ def _repr_html_(self) -> str:
plot3d,
psconvert,
rose,
scalebar,
set_panel,
shift_origin,
solar,
Expand Down
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
149 changes: 149 additions & 0 deletions pygmt/src/scalebar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""
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.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,
length: float | str,
height: float | str | None = None,
position: Position | Sequence[float | str] | AnchorCode | None = None,
scale_at: float | Sequence[float] | bool = False,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I do not really like this name.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GMT uses loc, GMT.jl uses scale_at_lat.

An alternative name may be scale_loc?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not so easy finding a good name here. I was thinking about something in the direction of

  • reference_coords: a bit long
  • reference_lat, scale_lat: partly also the longitude is given

Maybe location is enough, as "scale" is already contained in the method name "scalebar"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel users may be confused by position and location

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this is something I was also a bit unsure about. Maybe there are ideas or preferences by @weiji14 and @michaelgrund?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think scale_loc is the best name we have so far:

  • not too long
  • location can be all, latitude, longitude, middle of the map, reference point
  • "loc" as a shortcut for "location" should be OK
  • the leading "scale" should reduce a potential confusion with position

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Done in 0561ba4.

label: str | bool = False,
label_alignment: Literal["left", "right", "top", "bottom"] | None = None,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GMT and GMT.jl use align, currently PyGMT uses label_alignment, but maybe label_position?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel label_position is more consisten, as we have nan_position for Figure.colorbar. And it is a bit shorter.

unit: bool = False,
Comment thread
seisman marked this conversation as resolved.
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.

Comment thread
seisman marked this conversation as resolved.
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.
Comment thread
seisman marked this conversation as resolved.
Outdated
height
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:

- 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 </techref/justification_codes>` 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.
scale_at
Specify the location where the map scale is calculated. 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 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"``].
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
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
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.
Comment thread
seisman marked this conversation as resolved.
Outdated
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
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 Position
>>> fig = pygmt.Figure()
>>> fig.basemap(region=[0, 80, -30, 30], projection="M10c", frame=True)
>>> fig.scalebar(
... length=1000,
... position=Position((10, 10), cstype="mapcoords"),
... fancy=True,
... label="Scale",
... unit=True,
... )
>>> 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.
)

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_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"),
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)
)
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_scalebar.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: ad7658ed25f1a9f0a1ba74a0ffa84a4b
size: 10207
hash: md5
path: test_scalebar.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_scalebar_cartesian.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: e09a7c67f6146530ea594694853b6f98
size: 6508
hash: md5
path: test_scalebar_cartesian.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_scalebar_complete.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: c018b219d3ebc719fb1b1686e074dcd9
size: 11749
hash: md5
path: test_scalebar_complete.png
51 changes: 51 additions & 0 deletions pygmt/tests/test_scalebar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Test Figure.scalebar.
"""

import pytest
from pygmt import Figure
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(
length=1000,
height="10p",
position=Position((110, 22), cstype="mapcoords"),
fancy=True,
label="Scale",
label_alignment="left",
scale_at=(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(length=1, position=Position((2, 1), cstype="mapcoords"))
fig.scalebar(length=1, position=Position((4, 1), cstype="mapcoords"), vertical=True)
return fig
Loading