Skip to content

Commit 3c57bf5

Browse files
committed
Figure.shift_origin: Support shifting origins temporarily when used as a context manager
1 parent 8e1200f commit 3c57bf5

File tree

5 files changed

+167
-19
lines changed

5 files changed

+167
-19
lines changed

pygmt/src/shift_origin.py

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,42 @@
22
shift_origin - Shift plot origin in x and/or y directions.
33
"""
44

5+
from contextlib import contextmanager
6+
57
from pygmt.clib import Session
8+
from pygmt.helpers import build_arg_list
69

710

811
def shift_origin(
912
self, xshift: float | str | None = None, yshift: float | str | None = None
1013
):
1114
r"""
12-
Shift plot origin in x and/or y directions.
15+
Shift the plot origin in x and/or y directions.
16+
17+
The shifts can be temporary or permanent. If used as a context manager, the shifts
18+
are temporary and only apply to the block of code within the context manager. If
19+
used as a standalone method, the shifts are permanent and apply to all subsequent
20+
plots.
21+
22+
1. Use as a context manager to shift the plot origin temporarily:
23+
24+
.. code-block:: python
25+
26+
with fig.shift_origin(...):
27+
... # Other plot commands
28+
...
29+
30+
2. Use as a standalone method to shift the plot origin permanently:
31+
32+
.. code-block:: python
33+
34+
fig.shift_origin(xshift=12)
35+
... # Other plot commands
1336
14-
This method shifts the plot origin relative to the current origin by *xshift* and
15-
*yshift* in x and y directions, respectively. Optionally, append the length unit
37+
The shifts *xshift* and *yshift* in x and y directions are relative to the current
38+
plot origin. The default unit for shifts is centimeters (**c**) but can be changed
39+
to other units via :gmt-term:`PROJ_LENGTH_UNIT`. Optionally, append the length unit
1640
(**c** for centimeters, **i** for inches, or **p** for points) to the shifts.
17-
Default unit if not explicitly given is **c**, but can be changed to other units via
18-
:gmt-term:`PROJ_LENGTH_UNIT`.
1941
2042
For *xshift*, a special character **w** can also be used, which represents the
2143
bounding box **width** of the previous plot. The full syntax is
@@ -44,23 +66,63 @@ def shift_origin(
4466
4567
Examples
4668
--------
69+
70+
Shifting the plot origin permanently:
71+
4772
>>> import pygmt
4873
>>> fig = pygmt.Figure()
49-
>>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
50-
>>> # Shift the plot origin in x direction by 12 cm
51-
>>> fig.shift_origin(xshift=12)
52-
>>> fig.basemap(region=[0, 10, 0, 10], projection="X14c/10c", frame=True)
53-
>>> # Shift the plot origin in x direction based on the previous plot width
54-
>>> # Here, the width is 14 cm, and xshift is 16 cm
55-
>>> fig.shift_origin(xshift="w+2c")
74+
>>> fig.basemap(region=[0, 5, 0, 5], projection="X5c/5c", frame=True)
75+
>>> # Shift the plot origin in x direction by 6 cm
76+
>>> fig.shift_origin(xshift=6)
77+
<pygmt.src.shift_origin.shift_origin object at ...>
78+
>>> fig.basemap(region=[0, 7, 0, 5], projection="X7c/5c", frame=True)
79+
>>> # Shift the plot origin in x direction based on the previous plot width.
80+
>>> # Here, the width is 7 cm, and xshift is 8 cm.
81+
>>> fig.shift_origin(xshift="w+1c")
82+
<pygmt.src.shift_origin.shift_origin object at ...>
83+
>>> fig.basemap(region=[0, 10, 0, 5], projection="X10c/5c", frame=True)
84+
>>> fig.show()
85+
86+
Shifting the plot origin temporarily:
87+
88+
>>> fig = pygmt.Figure()
89+
>>> fig.basemap(region=[0, 5, 0, 5], projection="X5c/5c", frame=True)
90+
>>> # Shift the plot origin in x direction by 6 cm temporarily. The plot origin will
91+
>>> # revert back to the original plot origin after the block of code is executed.
92+
>>> with fig.shift_origin(xshift=6):
93+
... fig.basemap(region=[0, 5, 0, 5], projection="X5c/5c", frame=True)
94+
>>> # Shift the plot origin in y direction by 6 cm temporarily.
95+
>>> with fig.shift_origin(yshift=6):
96+
... fig.basemap(region=[0, 5, 0, 5], projection="X5c/5c", frame=True)
97+
>>> # Shift the plot origin in x and y directions by 6 cm temporarily.
98+
>>> with fig.shift_origin(xshift=6, yshift=6):
99+
... fig.basemap(region=[0, 5, 0, 5], projection="X5c/5c", frame=True)
56100
>>> fig.show()
57101
"""
58102
self._preprocess()
59-
args = ["-T"]
60-
if xshift:
61-
args.append(f"-X{xshift}")
62-
if yshift:
63-
args.append(f"-Y{yshift}")
103+
kwdict = {"T": True, "X": xshift, "Y": yshift}
64104

65105
with Session() as lib:
66-
lib.call_module(module="plot", args=args)
106+
lib.call_module(module="plot", args=build_arg_list(kwdict))
107+
_xshift = lib.get_common("X") # False or xshift in inches
108+
_yshift = lib.get_common("Y") # False or yshift in inches
109+
110+
@contextmanager
111+
def _shift_origin_context():
112+
"""
113+
An internal context manager to shift the plot origin temporarily.
114+
"""
115+
try:
116+
yield
117+
finally:
118+
# Revert the plot origin to the original plot origin by shifting it by
119+
# -xshift and -yshift in inches.
120+
kwdict = {
121+
"T": True,
122+
"X": f"{-1.0 * _xshift}i" if _xshift else None,
123+
"Y": f"{-1.0 * _yshift}i" if _yshift else None,
124+
}
125+
with Session() as lib:
126+
lib.call_module(module="plot", args=build_arg_list(kwdict))
127+
128+
return _shift_origin_context()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 6c63b9935618f607fe22608f7561be22
3+
size: 4869
4+
hash: md5
5+
path: test_shift_origin_context_manager.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 3a31df374874ae00920ada0b311b4266
3+
size: 5678
4+
hash: md5
5+
path: test_shift_origin_mixed_modes.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: cd83745f2657ff7daeaba368143db72f
3+
size: 4876
4+
hash: md5
5+
path: test_shift_origin_nested_context_manager.png

pygmt/tests/test_shift_origin.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33
"""
44

55
import pytest
6+
from pygmt import Figure
67
from pygmt.exceptions import GMTInvalidInput
7-
from pygmt.figure import Figure
8+
9+
10+
def _numbered_basemap(fig, number, size=3):
11+
"""
12+
A utility function to create a basemap with a number in the center.
13+
"""
14+
fig.basemap(region=[0, 1, 0, 1], projection=f"X{size}c", frame=0)
15+
fig.text(position="MC", text=number, font="24p")
816

917

1018
@pytest.mark.mpl_image_compare
@@ -27,6 +35,69 @@ def test_shift_origin():
2735
return fig
2836

2937

38+
@pytest.mark.mpl_image_compare
39+
def test_shift_origin_context_manager():
40+
"""
41+
Test if Figure.shift_origin as a context manager shifts origin temporarily.
42+
43+
Expected output is:
44+
| 3 | 4 |
45+
| 1 | 2 |
46+
"""
47+
fig = Figure()
48+
_numbered_basemap(fig, 1, size=2.5)
49+
with fig.shift_origin(xshift=3):
50+
_numbered_basemap(fig, 2, size=2.5)
51+
with fig.shift_origin(yshift=3):
52+
_numbered_basemap(fig, 3, size=2.5)
53+
with fig.shift_origin(xshift=3, yshift=3):
54+
_numbered_basemap(fig, 4, size=2.5)
55+
return fig
56+
57+
58+
@pytest.mark.mpl_image_compare
59+
def test_shift_origin_nested_context_manager():
60+
"""
61+
Test if Figure.shift_origin shift origin correctly when used in a nested context
62+
manager.
63+
64+
Expected output is:
65+
| 4 | 3 |
66+
| 1 | 2 |
67+
"""
68+
fig = Figure()
69+
_numbered_basemap(fig, 1, size=2.5)
70+
with fig.shift_origin(xshift=3):
71+
_numbered_basemap(fig, 2, size=2.5)
72+
with fig.shift_origin(yshift=3):
73+
_numbered_basemap(fig, 3, size=2.5)
74+
with fig.shift_origin(yshift=3):
75+
_numbered_basemap(fig, 4, size=2.5)
76+
return fig
77+
78+
79+
@pytest.mark.mpl_image_compare
80+
def test_shift_origin_mixed_modes():
81+
"""
82+
Test if Figure.shift_origin works when used as a context manager and as a
83+
method at the same time.
84+
85+
Expected output is:
86+
| | 3 | 4 |
87+
| 1 | 2 | |
88+
"""
89+
fig = Figure()
90+
_numbered_basemap(fig, 1, size=2.5)
91+
with fig.shift_origin(xshift=3):
92+
_numbered_basemap(fig, 2, size=2.5)
93+
fig.shift_origin(xshift=3)
94+
with fig.shift_origin(yshift=3):
95+
_numbered_basemap(fig, 3, size=2.5)
96+
fig.shift_origin(xshift=3, yshift=3)
97+
_numbered_basemap(fig, 4, size=2.5)
98+
return fig
99+
100+
30101
def test_shift_origin_unsupported_xshift_yshift():
31102
"""
32103
Raise an exception if X/Y/xshift/yshift is used.

0 commit comments

Comments
 (0)