Skip to content
Open
3 changes: 1 addition & 2 deletions examples/gallery/embellishments/gmt_logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@

# Add the GMT logo in the Top Right (TR) corner of the current plot, scaled up to be 3
# centimeters wide and offset by 0.3 cm in x-direction and 0.6 cm in y-direction.
fig.logo(position="jTR+o0.3c/0.6c+w3c")

fig.logo(position="TR", position_type="inside", anchor_offset=(0.3, 0.6), width="3c")
fig.show()
5 changes: 2 additions & 3 deletions pygmt/src/inset.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,8 @@ def inset(
... dcw="MG+gred",
... )
...
>>> # Map elements outside the "with" statement are plotted in the main
>>> # figure
>>> fig.logo(position="jBR+o0.2c+w3c")
>>> # Map elements outside the "with" statement are plotted in the main figure
>>> fig.logo(position="BR", position_type="inside", offset=0.2, width="3c")
>>> fig.show()
"""
self._activate_figure()
Expand Down
121 changes: 102 additions & 19 deletions pygmt/src/logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@
logo - Plot the GMT logo.
"""

from collections.abc import Sequence
from typing import Literal

from pygmt.alias import AliasSystem
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, kwargs_to_strings, use_alias


@fmt_docstring
@use_alias(
R="region",
D="position",
F="box",
S="style",
)
@use_alias(R="region", F="box", S="style")
@kwargs_to_strings(R="sequence", p="sequence")
def logo(
def logo( # noqa: PLR0913
self,
position: Sequence[str | float] | AnchorCode | None = None,
position_type: Literal[
"mapcoords", "boxcoords", "plotcoords", "inside", "outside"
] = "plotcoords",
anchor: AnchorCode | None = None,
anchor_offset: Sequence[float | str] | None = None,
height: float | str | None = None,
width: float | str | None = None,
projection=None,
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
Expand All @@ -29,27 +35,66 @@ def logo(
r"""
Plot the GMT logo.

By default, the GMT logo is 2 inches wide and 1 inch high and
will be positioned relative to the current plot origin.
Use various options to change this and to place a transparent or
opaque rectangular map panel behind the GMT logo.
.. figure:: https://docs.generic-mapping-tools.org/6.5/_images/GMT_coverlogo.png
:alt: GMT logo
:align: center
:width: 300px

By default, the GMT logo is 2 inches wide and 1 inch high and will be positioned
relative to the current plot origin. The position can be changed by specifying the
reference point (via ``position_type`` and ``position``) and anchor point (via
``anchor`` and ``anchor_offset``). Refer to :doc:`/techref/reference_anchor_points`
for details about the positioning.

Full GMT docs at :gmt-docs:`gmtlogo.html`.

{aliases}
- D = position/position_type/anchor/anchor_offset/width/height
- J = projection
- V = verbose
- c = panel
- t = transparency

Parameters
----------
{projection}
{region}
position : str
[**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\
**+w**\ *width*\ [**+j**\ *justify*]\ [**+o**\ *dx*\ [/*dy*]].
Set reference point on the map for the image.
position/position_type
Specify the reference point on the plot for the GMT logo. The method of
defining the the reference point is controlled by **position_type**, and the
exact location is set by **position**.

The **position_type** parameter can take one of the following values:

- ``"mapcoords"``: **position** is specified as (*longitude*, *latitude*) in map
coordinates. Example: (120, -45) places the reference point at 120°E, 45°S.
- ``"boxcoords"``: **position** is specified as (*nx*, *ny*) in normalized
coordinates, i.e., fractional values between 0 and 1 along the x- and y-axes.
Example: (0, 0) corresponds to the lower-left corner, and (1, 1) to the
upper-right corner of the plot bounding box.
- ``"plotcoords"``: **position** is specified as (*x*, *y*) in plot coordinates,
i.e., distances from the lower-left plot origin given in inches, centimeters,
or points. Example: ("1c", "2c") places the reference point 1 cm to the right
and 2 cm above the plot origin.
- ``"inside"`` or ``"outside"``: **position** is one of the nine
:doc:two-character justification codes </techref/justification_codes>,
indicating a specific location relative to the plot bounding box. Example:
``"TL"`` places the reference point at the top-left corner, either inside or
outside the bounding box.
anchor
Specify the anchor point of the GMT logo, using one of the
:doc:`2-character justification codes </techref/justification_codes>`.
The default value depends on **position_type**.

- ``position_type="inside"``: **anchor** defaults to the same as **position**.
- ``position_type="outside"``: **anchor** defaults to the mirror opposite of
**position**.
- Otherwise, **anchor** defaults to ``"MC"`` (middle center).
anchor_offset
Specifies an offset for the anchor point as *offset* or
(*offset_x*, *offset_y*). If a single value *offset* is given, both *offset_x*
and *offset_y* are set to *offset*.
width/height
Width or height of the GMT logo. Since the aspect ratio is fixed, only one of
the two can be specified.
box : bool or str
If set to ``True``, draw a rectangular border around the
GMT logo.
Expand All @@ -61,13 +106,51 @@ def logo(
[Default]
- **n** to skip the label placement
- **u** to place the URL to the GMT site
{projection}
{region}
{verbose}
{panel}
{transparency}
"""
self._activate_figure()

aliasdict = AliasSystem().add_common(
# width and height are mutually exclusive.
if width is not None and height is not None:
msg = "Cannot specify both width and height."
raise GMTInvalidInput(msg)

# Mapping position_type to GMT single-letter code.
_position_type = {
"mapcoords": "g",
"boxcoords": "n",
"plotcoords": "x",
"inside": "j",
"outside": "J",
}[position_type]

# Prior PyGMT v0.17.0, 'position' was aliased to the -D option.
# For backward compatibility, we need to check if users pass a string with the GMT
# CLI syntax to 'position', i.e., a string starting with one of the leading
# single-letter or contains modifiers with "+".
if isinstance(position, str) and (position[0] in "gnxjJ" or "+" in position):
if any(v is not None for v in (anchor, anchor_offset, height, width)):
msg = (
"Parameter 'position' is given with a raw GMT CLI syntax, and conflicts "
"with other parameters (anchor, anchor_offset, height, width). "
"Please refer to the documentation for the recommended usage."
)
raise GMTInvalidInput(msg)
_position_type = "" # Unset _position_type to an empty string.

aliasdict = AliasSystem(
D=[
Alias(position, name="position", sep="/", size=2, prefix=_position_type),
Alias(anchor, name="anchor", prefix="+j"),
Alias(anchor_offset, name="anchor_offset", prefix="+o", sep="/", size=2),
Alias(height, name="height", prefix="+h"),
Alias(width, name="width", prefix="+w"),
]
).add_common(
J=projection,
V=verbose,
c=panel,
Expand Down
46 changes: 45 additions & 1 deletion pygmt/tests/test_logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest
from pygmt import Figure
from pygmt.exceptions import GMTInvalidInput


@pytest.mark.benchmark
Expand All @@ -24,5 +25,48 @@ def test_logo_on_a_map():
"""
fig = Figure()
fig.basemap(region=[-90, -70, 0, 20], projection="M15c", frame=True)
fig.logo(position="jTR+o0.25c/0.25c+w7.5c", box=True)
fig.logo(
position_type="inside",
position="TR",
anchor_offset=(0.25, 0.25),
width="7.5c",
box=True,
)
return fig


@pytest.mark.mpl_image_compare(filename="test_logo_position_deprecated_syntax.png")
def test_logo_position():
"""
Test that the new group of parameters works as expected.
"""
fig = Figure()
fig.basemap(region=[-90, -70, 0, 20], projection="M15c", frame=True)
fig.logo(position="TL", position_type="inside")
fig.logo(position="TR", position_type="inside", width="7.5c")
fig.logo(position=(6, 0), width="7.5c")
fig.logo(position=(0, 0))
return fig


@pytest.mark.mpl_image_compare
def test_logo_position_deprecated_syntax():
"""
Test that passing the deprecated GMT CLI syntax string to 'position' works.
"""
fig = Figure()
fig.basemap(region=[-90, -70, 0, 20], projection="M15c", frame=True)
fig.logo(position="jTL")
fig.logo(position="jTR+w7.5c")
fig.logo(position="6/0+w7.5c")
fig.logo(position="0/0") # This is actually the new syntax.
return fig


def test_logo_width_and_height():
"""
Test that an error is raised when both width and height are specified.
"""
fig = Figure()
with pytest.raises(GMTInvalidInput):
fig.logo(width="5c", height="5c")
Loading