Skip to content

Commit

Permalink
Wrap inset (#788)
Browse files Browse the repository at this point in the history
*Wrap inset function to use a context manager
*Create src/inset.py and import inset function into base_plotting.py

Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
Co-authored-by: Dongdong Tian <seisman.info@gmail.com>
  • Loading branch information
3 people authored Feb 5, 2021
1 parent 213a414 commit 12adb41
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 2 deletions.
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Plotting data and laying out the map:
Figure.shift_origin
Figure.text
Figure.meca
Figure.inset

Color palette table generation:

Expand Down
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
tutorials/contour-map.rst
tutorials/earth-relief.rst
tutorials/3d-perspective-image.rst
tutorials/inset.rst
tutorials/configuration.rst

.. toctree::
Expand Down
25 changes: 25 additions & 0 deletions examples/gallery/plot/inset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Inset
-----
The :meth:`pygmt.Figure.inset` method adds an inset figure inside a larger
figure. The function is called using a ``with`` statement, and its position,
box, offset, and margin parameters are set. Within the ``with`` statement,
PyGMT plotting functions can be called that add to the inset figure.
"""
import pygmt

fig = pygmt.Figure()
# Create the primary figure, setting the region to Madagascar, the land color to
# "brown", the water to "lightblue", the shorelines width to "thin", and adding a frame
fig.coast(region="MG+r2", land="brown", water="lightblue", shorelines="thin", frame="a")
# Create an inset, setting the position to top left, the width to 3.5 centimeters, and
# the x- and y-offsets to 0.2 centimeters. The margin is set to 0, and the border is "green".
with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"):
# Create a figure in the inset using coast. This example uses the azimuthal
# orthogonal projection centered at 47E, 20S. The land is set to "gray" and
# Madagascar is highlighted in "red".
fig.coast(
region="g", projection="G47/-20/3.5c", land="gray", water="white", dcw="MG+gred"
)
fig.show()
110 changes: 110 additions & 0 deletions examples/tutorials/inset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""
Adding an inset to the figure
=============================
To plot an inset figure inside another larger figure, we can use the
:meth:`pygmt.Figure.inset` method. After a large figure has been created,
call ``inset`` using a ``with`` statement, and new plot elements will be
added to the inset figure instead of the larger figure.
"""
# sphinx_gallery_thumbnail_number = 4

import pygmt

########################################################################################
#
# Prior to creating an inset figure, a larger figure must first be plotted. In the
# example below, :meth:`pygmt.Figure.coast` is used to create a map of the US state of
# Massachusetts.

fig = pygmt.Figure()
fig.coast(
region=[-74, -69.5, 41, 43], # Set bounding box of the large figure
borders="2/thin", # Plot state boundaries with thin lines
shorelines="thin", # Plot coastline with thin lines
projection="M15c", # Set Mercator projection and size of 15 centimeter
land="lightyellow", # Color land areas light yellow
water="lightblue", # Color water areas light blue
frame="a", # Set frame with annotation and major tick spacing
)
fig.show()

########################################################################################
#
# The :meth:`pygmt.Figure.inset` method uses a context manager, and is called using a
# ``with`` statement. The ``position`` argument, including the inset width, is required
# to plot the inset. Using the **j** argument, the location of the inset is
# set to one of the 9 anchors (bottom-middle-top and left-center-right). In the
# example below, ``BL`` sets the inset to the bottom left. The ``box`` argument can
# set the fill and border of the inset. In the example below, ``+pblack`` sets the
# border color to black and ``+gred`` sets the fill to red.

fig = pygmt.Figure()
fig.coast(
region=[-74, -69.5, 41, 43],
borders="2/thin",
shorelines="thin",
projection="M15c",
land="lightyellow",
water="lightblue",
frame="a",
)
with fig.inset(position="jBL+w3c", box="+pblack+glightred"):
# pass is used to exit the with statement as no plotting functions are called
pass
fig.show()

########################################################################################
#
# When using **j** to set the anchor of the inset, the default location is in
# contact with the nearby axis or axes. The offset of the inset can be set with **+o**,
# followed by the offsets along the x- and y-axis. If only one offset is
# passed, it is applied to both axes. Each offset can have its own unit. In
# the example below, the inset is shifted 0.5 centimeters on the x-axis and
# 0.2 centimeters on the y-axis.

fig = pygmt.Figure()
fig.coast(
region=[-74, -69.5, 41, 43],
borders="2/thin",
shorelines="thin",
projection="M15c",
land="lightyellow",
water="lightblue",
frame="a",
)
with fig.inset(position="jBL+w3c+o0.5c/0.2c", box="+pblack+glightred"):
pass
fig.show()

########################################################################################
#
# Standard plotting functions can be called from within the ``inset`` context manager.
# The example below uses :meth:`pygmt.Figure.coast` to plot a zoomed out map that
# selectively paints the state of Massachusetts to shows its location relative to
# other states.

fig = pygmt.Figure()
fig.coast(
region=[-74, -69.5, 41, 43],
borders="2/thin",
shorelines="thin",
projection="M15c",
land="lightyellow",
water="lightblue",
frame="a",
)
# This does not include an inset fill as it is covered by the inset figure
with fig.inset(position="jBL+w3c+o0.5c/0.2c", box="+pblack"):
# Use a plotting function to create a figure inside the inset
fig.coast(
region=[-80, -65, 35, 50],
projection="M3c",
land="gray",
borders=[1, 2],
shorelines="1/thin",
water="white",
# Use dcw to selectively highlight an area
dcw="US.MA+gred",
)
fig.show()
3 changes: 1 addition & 2 deletions pygmt/base_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -1642,5 +1642,4 @@ def text(
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("text", arg_str)

# GMT Supplementary modules
from pygmt.src import meca # pylint: disable=import-outside-toplevel
from pygmt.src import inset, meca # pylint: disable=import-outside-toplevel
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
Source code for PyGMT modules.
"""
# pylint: disable=import-outside-toplevel
from pygmt.src.inset import inset
from pygmt.src.meca import meca
132 changes: 132 additions & 0 deletions pygmt/src/inset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""
inset - Create inset figures.
"""
import contextlib

from pygmt.clib import Session
from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias


@fmt_docstring
@contextlib.contextmanager
@use_alias(D="position", F="box", M="margin", N="no_clip", V="verbose")
@kwargs_to_strings(D="sequence", M="sequence")
def inset(self, **kwargs):
r"""
Create an inset figure to be placed within a larger figure.
This function sets the position, frame, and margins for a smaller figure
inside of the larger figure. Plotting functions that are called within the
context manager are added to the inset figure.
Full option list at :gmt-docs:`inset.html`
{aliases}
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 argument.*
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
2-char justification code \ 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, cm, 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 meter (e)].
Append **+w**\ *width*\ [/*height*] of bounding rectangle or box
in plot coordinates (inches, cm, 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 2-char
justification code *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`` option [outline only].
box : str or bool
[**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\
*pen*]][**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\
[[*dx*/*dy*/][*shade*]]]
If passed ``True``, this draws a rectangular box around the map
inset using the default pen; specify a different pen
with **+p**\ *pen*. Add **+g**\ *fill* to fill the logo box
[Default is no fill].
Append **+c**\ *clearance* where *clearance* is either
*gap*, *xgap*\ /\ *ygap*, or *lgap*\ /\ *rgap*\ /\ *bgap*\ /\
*tgap* where these items are uniform, separate in x- and
y-direction, or individual side spacings between logo and border.
Append **+i** to draw a secondary, inner border as well. We use a
uniform *gap* between borders of 2\ **p** and the default pen
unless other values are specified. Append **+r** to draw rounded
rectangular borders instead, with a 6\ **p** corner radius. You
can override this radius by appending another value. Append
**+s** to draw an offset background shaded region. Here, *dx*/*dy*
indicates the shift relative to the foreground frame
[4\ **p**/-4\ **p**] and *shade* sets the fill style to use for
shading [Default is gray50].
margin : int or str or list
This is clearance that is added around the inside of the inset.
Plotting will take place within the inner region only. The margins
can be a single value, a pair of values separated (for setting
separate horizontal and vertical margins), or the full set of four
margins (for setting separate left, right, bottom, and top
margins). When passing multiple values, it can be either a list or
a string with the values separated by forward
slashes [Default is no margins].
no_clip : bool
Do NOT clip features extruding outside map inset boundaries [Default
will clip].
{V}
Examples
--------
>>> import pygmt
>>>
>>> # 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
>>> # 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="+pgreen"):
... # Map elements under the "with" statement are plotted in the inset
... fig.coast(
... region="g",
... projection="G47/-20/3.5c",
... land="gray",
... water="white",
... dcw="MG+gred",
... )
...
>>> # Map elements outside the "with" block are plotted in the main figure
>>> fig.logo(position="jBR+o0.2c+w3c")
>>> fig.show()
<IPython.core.display.Image object>
"""
kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access
with Session() as lib:
try:
lib.call_module("inset", f"begin {build_arg_string(kwargs)}")
yield
finally:
v_arg = build_arg_string({"V": kwargs.get("V")})
lib.call_module("inset", f"end {v_arg}".strip())
42 changes: 42 additions & 0 deletions pygmt/tests/test_inset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Tests for the inset function.
"""
from pygmt import Figure
from pygmt.helpers.testing import check_figures_equal


@check_figures_equal()
def test_inset_aliases():
"""
Test the aliases for the inset function.
"""
fig_ref, fig_test = Figure(), Figure()
fig_ref.basemap(R="MG+r2", B="afg")
with fig_ref.inset(D="jTL+w3.5c+o0.2c", M=0, F="+pgreen"):
fig_ref.basemap(R="g", J="G47/-20/4c", B="afg")

fig_test.basemap(region="MG+r2", frame="afg")
with fig_test.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"):
fig_test.basemap(region="g", projection="G47/-20/4c", frame="afg")
return fig_ref, fig_test


@check_figures_equal()
def test_inset_context_manager():
"""
Test that the inset context manager works and, once closed, plotting
elements are added to the larger figure.
"""
fig_ref, fig_test = Figure(), Figure()

fig_ref.basemap(region=[-74, -69.5, 41, 43], projection="M9c", frame=True)
fig_ref.basemap(rose="jTR+w3c") # Pass rose argument with basemap before the inset
with fig_ref.inset(position="jBL+w3c+o0.2c", margin=0, box="+pblack"):
fig_ref.basemap(region=[-80, -65, 35, 50], projection="M3c", frame="afg")

fig_test.basemap(region=[-74, -69.5, 41, 43], projection="M9c", frame=True)
with fig_test.inset(position="jBL+w3c+o0.2c", margin=0, box="+pblack"):
fig_test.basemap(region=[-80, -65, 35, 50], projection="M3c", frame="afg")
fig_test.basemap(rose="jTR+w3c") # Pass rose argument with basemap after the inset

return fig_ref, fig_test

0 comments on commit 12adb41

Please sign in to comment.