Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4dd4222
Figure.inset: Refactor using the new alias system
seisman Aug 10, 2025
5825125
Merge branch 'main' into refactor/inset
seisman Oct 15, 2025
eec0fb7
Initial implemention of the Position class
seisman Nov 16, 2025
69d4d42
Merge branch 'main' into params/position
seisman Nov 20, 2025
539f66f
Fix styling
seisman Nov 20, 2025
97f015f
Add tests and improve docstrings
seisman Nov 23, 2025
854804e
Add to API doc
seisman Nov 23, 2025
6b55dde
Add an inline doctest
seisman Nov 23, 2025
3d629cb
position is not required
seisman Nov 23, 2025
576b822
Default to plotcoords
seisman Nov 23, 2025
f54bec9
Updates
seisman Nov 23, 2025
5a2e20b
Merge branch 'main' into params/position
seisman Nov 24, 2025
2c59b7f
Improve the checking in Figure.logo
seisman Nov 24, 2025
d0b62ec
Merge branch 'main' into params/position
seisman Nov 24, 2025
fe18c87
Improve docstrings
seisman Nov 24, 2025
038161b
Improve docstrings
seisman Nov 24, 2025
a6e75bc
Improve docstrings
seisman Nov 25, 2025
3ec8c06
Improve docstrings
seisman Nov 25, 2025
339ce00
Improve docstrings
seisman Nov 25, 2025
4d616de
Revert changes in logo.py
seisman Nov 25, 2025
ad9e0aa
Simplify tests
seisman Nov 25, 2025
b084e5f
Validate values
seisman Nov 25, 2025
d4ad6e0
type will be validated in the Alias System
seisman Nov 25, 2025
7dc37bd
Use the image from the GMT docs
seisman Nov 25, 2025
bfecb2d
Fix width and alignment
seisman Nov 25, 2025
18b90b3
Improve docstrings
seisman Nov 25, 2025
6b1b5bc
Remove unneeded blank lines
seisman Nov 25, 2025
1eae742
Improve docstrings
seisman Nov 25, 2025
721b46f
Validate anchor code
seisman Nov 25, 2025
8793aeb
Merge branch 'params/position' into refactor/inset
seisman Nov 25, 2025
a06cb56
Refactor using the Position class
seisman Nov 25, 2025
2c88eb1
Improve the code for Figure.inset
seisman Dec 6, 2025
38bc492
Improve Figure.inset
seisman Dec 6, 2025
b474ae5
Revert changes in position
seisman Dec 11, 2025
f4ec056
Merge branch 'main' into refactor/inset
seisman Dec 11, 2025
5d6e72a
Fix
seisman Dec 11, 2025
e670584
Add more doctests
seisman Dec 12, 2025
2b62b85
Merge branch 'main' into refactor/inset
seisman Dec 13, 2025
6684afb
Simplify docstrings
seisman Dec 14, 2025
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
109 changes: 55 additions & 54 deletions pygmt/src/inset.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@

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
from pygmt.params import Box
from pygmt.params import Box, Position

__doctest_skip__ = ["inset"]


@fmt_docstring
@contextlib.contextmanager
@use_alias(D="position", M="margin")
@kwargs_to_strings(D="sequence", M="sequence")
@use_alias(M="margin")
@kwargs_to_strings(M="sequence")
def inset(
self,
position: Position | None = None,
width: float | str | None = None,
height: float | str | None = None,
box: Box | bool = False,
projection: str | None = None,
region: Sequence[float | str] | str | None = None,
box: Box | bool = False,
no_clip: bool = False,
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
Expand All @@ -31,13 +35,15 @@ def inset(
r"""
Manage figure inset setup and completion.
This method sets the position, frame, and margins for a smaller figure
inside of the larger figure. Plotting methods that are called within the
This method carves out a sub-region of the current plot canvas and restrict further
plotting to that section of the canvas. Plotting methods that are called within the
context manager are added to the inset figure.
Full GMT docs at :gmt-docs:`inset.html`.
$aliases
- D = position, **+w**: width/height
- F = box
- J = projection
- N = no_clip
Expand All @@ -46,45 +52,12 @@ def inset(
Parameters
----------
position : str or list
*xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]] \
| [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\
**+w**\ *width*\ [/*height*][**+j**\ *justify*]\
[**+o**\ *dx*\ [/*dy*]].
*This is the only required parameter.*
Define the map inset rectangle on the map. Specify the rectangle
in one of three ways:
Append **g**\ *lon*/*lat* for map (user) coordinates,
**j**\ *code* or **J**\ *code* for setting the *refpoint* via a
:doc:`2-character justification code </techref/justification_codes>`
that refers to the (invisible)
projected map bounding box, **n**\ *xn*/*yn* for normalized (0-1)
bounding box coordinates, or **x**\ *x*/*y* for plot
coordinates (inches, centimeters, points, append unit).
All but **x** requires both ``region`` and ``projection`` to be
specified. You can offset the reference point via
**+o**\ *dx*/*dy* in the direction implied by *code* or
**+j**\ *justify*.
Alternatively, give *west/east/south/north* of geographic
rectangle bounded by parallels and meridians; append **+r** if the
coordinates instead are the lower left and upper right corners of
the desired rectangle. (Or, give *xmin/xmax/ymin/ymax* of bounding
rectangle in projected coordinates and optionally
append **+u**\ *unit* [Default coordinate unit is meters (**e**)].
Append **+w**\ *width*\ [/*height*] of bounding rectangle or box
in plot coordinates (inches, centimeters, etc.). By default, the
anchor point on the scale is assumed to be the bottom left corner
(**BL**), but this can be changed by appending **+j** followed by a
:doc:`2-character justification code </techref/justification_codes>`
*justify*.
**Note**: If **j** is used then *justify* defaults to the same
as *refpoint*, if **J** is used then *justify* defaults to the
mirror opposite of *refpoint*. Specify inset box attributes via
the ``box`` parameter [Default is outline only].
position
Specify the position of the inset on the map. See :class:`pygmt.params.Position`
for details.
width
height
Width and height of the inset. *height* is optional.
box
Draw a background box behind the inset. If set to ``True``, a simple rectangular
box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box appearance,
Expand All @@ -109,31 +82,59 @@ def inset(
Examples
--------
>>> import pygmt
>>> from pygmt.params import Box
>>> from pygmt.params import Box, Position
>>>
>>> # Create the larger figure
>>> fig = pygmt.Figure()
>>> fig.coast(region="MG+r2", water="lightblue", shorelines="thin")
>>> # Use a "with" statement to initialize the inset context manager
>>> # Use a "with" statement to initialize the inset context manager.
>>> # Setting the position to Top Left and a width of 3.5 centimeters
>>> with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box=Box(pen="green")):
... # Map elements under the "with" statement are plotted in the inset
>>> with fig.inset(
... position=Position("TL", offset=0.2),
... width="3.5c",
... margin=0,
... box=Box(pen="green"),
... ): # Map elements under the "with" statement are plotted in the inset
... fig.coast(
... region="g",
... projection="G47/-20/3.5c",
... projection="G47/-20/?",
... land="gray",
... water="white",
... dcw="MG+gred",
... )
...
>>> # Map elements outside the "with" statement are plotted in the main
>>> # figure
>>> # Map elements outside the "with" statement are plotted in the main figure.
>>> fig.logo(position="jBR+o0.2c+w3c")
>>> fig.show()
"""
self._activate_figure()

if position is None:
msg = "Parameter 'position' must be specified."
raise GMTInvalidInput(msg)

# Prior PyGMT v0.17.0, 'position' can accept a raw GMT CLI string. Check for
# conflicts with other parameters.
_old_position_syntax = isinstance(position, str)
if _old_position_syntax and any(v is not None for v in (width, height)):
msg = (
"Parameter 'position' is given with a raw GMT command string, and conflicts "
"with parameters 'height', and 'width'. "
)
raise GMTInvalidInput(msg)

if not _old_position_syntax and width is None:
msg = "Parameter 'width' must be specified."
raise GMTInvalidInput(msg)

if height is not None and width is None:
msg = "'width' must be specified if 'height' is given."
raise GMTInvalidInput(msg)

aliasdict = AliasSystem(
D=[
Alias(position, name="position"),
Alias(width, name="width", prefix="+w"), # +wwidth/height
Alias(height, name="height", prefix="/"),
],
F=Alias(box, name="box"),
N=Alias(no_clip, name="no_clip"),
).add_common(
Expand Down
54 changes: 51 additions & 3 deletions pygmt/tests/test_inset.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import pytest
from pygmt import Figure
from pygmt.params import Box
from pygmt.exceptions import GMTInvalidInput
from pygmt.params import Box, Position


@pytest.mark.benchmark
Expand All @@ -15,7 +16,12 @@ def test_inset_aliases():
"""
fig = Figure()
fig.basemap(region="MG+r2", frame="afg")
with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box=Box(pen="green")):
with fig.inset(
position=Position("TL", offset=0.2),
width="3.5c",
margin=0,
box=Box(pen="green"),
):
fig.basemap(region="g", projection="G47/-20/4c", frame="afg")
return fig

Expand All @@ -28,7 +34,49 @@ def test_inset_context_manager():
"""
fig = Figure()
fig.basemap(region=[-74, -69.5, 41, 43], projection="M9c", frame=True)
with fig.inset(position="jBL+w3c+o0.2c", margin=0, box=Box(pen="black")):
with fig.inset(
position=Position("BL", offset=0.2), width="3c", margin=0, box=Box(pen="black")
):
fig.basemap(region=[-80, -65, 35, 50], projection="M3c", frame="afg")
fig.basemap(rose="jTR+w3c") # Pass rose argument with basemap after the inset
return fig


@pytest.mark.mpl_image_compare(filename="test_inset_aliases.png")
def test_inset_deprecated_position():
"""
Test that the deprecated raw GMT CLI string for position still works.
"""
fig = Figure()
fig.basemap(region="MG+r2", frame="afg")
with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box=Box(pen="green")):
fig.basemap(region="g", projection="G47/-20/4c", frame="afg")
return fig


def test_inset_invalid_inputs():
"""
Test that an error is raised when invalid inputs are provided.
"""
fig = Figure()
fig.basemap(region="MG+r2", frame="afg")

# Position is not given
with pytest.raises(GMTInvalidInput):
with fig.inset(width="3.5c"):
pass

# Width is not given
with pytest.raises(GMTInvalidInput):
with fig.inset(position=Position("TL")):
pass

# Height is given but width is not given
with pytest.raises(GMTInvalidInput):
with fig.inset(position=Position("TL"), height="5c"):
pass

# Old position syntax conflicts with width/height
with pytest.raises(GMTInvalidInput):
with fig.inset(position="jTL+w3.5c", width="3.5c"):
pass
Loading