Skip to content

Commit 7a42225

Browse files
weiji14seisman
authored and
Josh Sixsmith
committed
Wrap subplot using with statement (GenericMappingTools#822)
Wrapping the `subplot` function, in a `with` statement! Original GMT `subplot` function can be found at https://docs.generic-mapping-tools.org/6.1/subplot.html. This is the 3rd attempt at implementing `subplot` in PyGMT, with commits heavily re-adapted/cherry-picked from GenericMappingTools#412 and GenericMappingTools#427. * Add fig.subplot and fig.set_panel to API docs * Alias fixedlabel (A), clearance (C), verbose (V) for set_panel * Turn fig.set_panel into a context manager * Alias projection (J), region (R), verbose (V), x/yshift (X/Y) for subplot * Allow for spaces in title and labels without needing double quotes Mitigates against GenericMappingTools#247. * Allow for list inputs into fig.set_panel(panel=...) * Rename sca to set_panel and ax to panel * Fix bug that prevented boolean to -A from working * Add note that subplot panel is activated until further notice * Validate subplot nrows/ncols and figsize/subsize argument inputs * Revise advanced subplot layout subsection to use two subplots calls Co-authored-by: Dongdong Tian <[email protected]>
1 parent 6c6d1ee commit 7a42225

File tree

7 files changed

+585
-0
lines changed

7 files changed

+585
-0
lines changed

doc/api/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ Plotting data and laying out the map:
3636
Figure.meca
3737
Figure.plot
3838
Figure.plot3d
39+
Figure.set_panel
3940
Figure.shift_origin
41+
Figure.subplot
4042
Figure.text
4143

4244
Color palette table generation:

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
tutorials/earth-relief.rst
4040
tutorials/3d-perspective-image.rst
4141
tutorials/inset.rst
42+
tutorials/subplots.rst
4243
tutorials/configuration.rst
4344

4445
.. toctree::

examples/tutorials/subplots.py

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
"""
2+
Making subplots
3+
===============
4+
5+
When you're preparing a figure for a paper, there will often be times when
6+
you'll need to put many individual plots into one large figure, and label them
7+
'abcd'. These individual plots are called subplots.
8+
9+
There are two main ways to create subplots in GMT:
10+
11+
- Use :meth:`pygmt.Figure.shift_origin` to manually move each individual plot
12+
to the right position.
13+
- Use :meth:`pygmt.Figure.subplot` to define the layout of the subplots.
14+
15+
The first method is easier to use and should handle simple cases involving a
16+
couple of subplots. For more advanced subplot layouts, however, we recommend the
17+
use of :meth:`pygmt.Figure.subplot` which offers finer grained control, and
18+
this is what the tutorial below will cover.
19+
"""
20+
# sphinx_gallery_thumbnail_number = 3
21+
22+
import pygmt
23+
24+
###############################################################################
25+
#
26+
# Let's start by initializing a :class:`pygmt.Figure` instance.
27+
28+
fig = pygmt.Figure()
29+
30+
###############################################################################
31+
# Define subplot layout
32+
# ---------------------
33+
#
34+
# The :meth:`pygmt.Figure.subplot` function is used to set up the layout, size,
35+
# and other attributes of the figure. It divides the whole canvas into regular
36+
# grid areas with *n* rows and *m* columns. Each grid area can contain an
37+
# individual subplot. For example:
38+
39+
###############################################################################
40+
# .. code-block:: default
41+
#
42+
# with fig.subplot(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb"):
43+
# ...
44+
45+
###############################################################################
46+
# will define our figure to have a 2 row and 3 column grid layout.
47+
# ``figsize=("15c", "6c")`` defines the overall size of the figure to be 15 cm
48+
# wide by 6 cm high. Using ``frame="lrtb"`` allows us to customize the map frame
49+
# for all subplots instead of setting them individually. The figure layout will
50+
# look like the following:
51+
52+
with fig.subplot(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb"):
53+
for i in range(2): # row number starting from 0
54+
for j in range(3): # column number starting from 0
55+
index = i * 3 + j # index number starting from 0
56+
with fig.set_panel(panel=index): # sets the current panel
57+
fig.text(
58+
position="MC",
59+
text=f"index: {index}; row: {i}, col: {j}",
60+
region=[0, 1, 0, 1],
61+
)
62+
fig.show()
63+
64+
###############################################################################
65+
# The :meth:`pygmt.Figure.set_panel` function activates a specified subplot, and
66+
# all subsequent plotting functions will take place in that subplot panel. This
67+
# is similar to matplotlib's ``plt.sca`` method. In order to specify a subplot,
68+
# you will need to provide the identifier for that subplot via the ``panel``
69+
# parameter. Pass in either the *index* number, or a tuple/list like
70+
# (*row*, *col*) to ``panel``.
71+
72+
###############################################################################
73+
# .. note::
74+
#
75+
# The row and column numbering starts from 0. So for a subplot layout with
76+
# N rows and M columns, row numbers will go from 0 to N-1, and column
77+
# numbers will go from 0 to M-1.
78+
79+
###############################################################################
80+
# For example, to activate the subplot on the top right corner (index: 2) at
81+
# *row*\=0 and *col*\=2, so that all subsequent plotting commands happen
82+
# there, you can use the following command:
83+
84+
###############################################################################
85+
# .. code-block:: default
86+
#
87+
# with fig.set_panel(panel=[0, 2]):
88+
# ...
89+
90+
###############################################################################
91+
# Making your first subplot
92+
# -------------------------
93+
# Next, let's use what we learned above to make a 2 row by 2 column subplot
94+
# figure. We'll also pick up on some new parameters to configure our subplot.
95+
96+
fig = pygmt.Figure()
97+
with fig.subplot(
98+
nrows=2,
99+
ncols=2,
100+
figsize=("15c", "6c"),
101+
autolabel=True,
102+
frame=["af", "WSne"],
103+
margins=["0.1c", "0.2c"],
104+
title="My Subplot Heading",
105+
):
106+
fig.basemap(region=[0, 10, 0, 10], projection="X?", panel=[0, 0])
107+
fig.basemap(region=[0, 20, 0, 10], projection="X?", panel=[0, 1])
108+
fig.basemap(region=[0, 10, 0, 20], projection="X?", panel=[1, 0])
109+
fig.basemap(region=[0, 20, 0, 20], projection="X?", panel=[1, 1])
110+
fig.show()
111+
112+
###############################################################################
113+
# In this example, we define a 2-row, 2-column (2x2) subplot layout using
114+
# :meth:`pygmt.Figure.subplot`. The overall figure dimensions is set to be
115+
# 15 cm wide and 6 cm high (``figsize=["15c", "6c"]``). In addition, we use
116+
# some optional parameters to fine-tune some details of the figure creation:
117+
#
118+
# - ``autolabel=True``: Each subplot is automatically labelled abcd
119+
# - ``margins=["0.1c", "0.2c"]``: adjusts the space between adjacent subplots.
120+
# In this case, it is set as 0.1 cm in the X direction and 0.2 cm in the Y
121+
# direction.
122+
# - ``title="My Subplot Heading"``: adds a title on top of the whole figure.
123+
#
124+
# Notice that each subplot was set to use a linear projection ``"X?"``.
125+
# Usually, we need to specify the width and height of the map frame, but it is
126+
# also possible to use a question mark ``"?"`` to let GMT decide automatically
127+
# on what is the most appropriate width/height for the each subplot's map
128+
# frame.
129+
130+
###############################################################################
131+
# .. tip::
132+
#
133+
# In the above example, we used the following commands to activate the
134+
# four subplots explicitly one after another::
135+
#
136+
# fig.basemap(..., panel=[0, 0])
137+
# fig.basemap(..., panel=[0, 1])
138+
# fig.basemap(..., panel=[1, 0])
139+
# fig.basemap(..., panel=[1, 1])
140+
#
141+
# In fact, we can just use ``fig.basemap(..., panel=True)`` without
142+
# specifying any subplot index number, and GMT will automatically activate
143+
# the next subplot panel.
144+
145+
###############################################################################
146+
# .. note::
147+
#
148+
# All plotting functions (e.g. :meth:`pygmt.Figure.coast`,
149+
# :meth:`pygmt.Figure.text`, etc) are able to use ``panel`` parameter when
150+
# in subplot mode. Once a panel is activated using ``panel`` or
151+
# :meth:`pygmt.Figure.set_panel`, subsequent plotting commands that don't
152+
# set a ``panel`` will have their elements added to the same panel as
153+
# before.
154+
155+
###############################################################################
156+
# Shared X and Y axis labels
157+
# --------------------------
158+
# In the example above with the four subplots, the two subplots for each row
159+
# have the same Y-axis range, and the two subplots for each column have the
160+
# same X-axis range. You can use the ``sharex``/``sharey`` parameters to set a
161+
# common X and/or Y axis between subplots.
162+
163+
fig = pygmt.Figure()
164+
with fig.subplot(
165+
nrows=2,
166+
ncols=2,
167+
figsize=("15c", "6c"), # width of 15 cm, height of 6 cm
168+
autolabel=True,
169+
margins=["0.3c", "0.2c"], # horizontal 0.3 cm and vertical 0.2 cm margins
170+
title="My Subplot Heading",
171+
sharex="b", # shared x-axis on the bottom side
172+
sharey="l", # shared y-axis on the left side
173+
frame="WSrt",
174+
):
175+
fig.basemap(region=[0, 10, 0, 10], projection="X?", panel=True)
176+
fig.basemap(region=[0, 20, 0, 10], projection="X?", panel=True)
177+
fig.basemap(region=[0, 10, 0, 20], projection="X?", panel=True)
178+
fig.basemap(region=[0, 20, 0, 20], projection="X?", panel=True)
179+
fig.show()
180+
181+
###############################################################################
182+
# ``sharex="b"`` indicates that subplots in a column will share the x-axis, and
183+
# only the **b**\ ottom axis is displayed. ``sharey="l"`` indicates that
184+
# subplots within a row will share the y-axis, and only the **l**\ eft axis is
185+
# displayed.
186+
#
187+
# Of course, instead of using the ``sharex``/``sharey`` option, you can also
188+
# set a different ``frame`` for each subplot to control the axis properties
189+
# individually for each subplot.
190+
191+
###############################################################################
192+
# Advanced subplot layouts
193+
# ------------------------
194+
#
195+
# Nested subplot are currently not supported. If you want to create more
196+
# complex subplot layouts, some manual adjustments are needed.
197+
#
198+
# The following example draws three subplots in a 2-row, 2-column layout, with
199+
# the first subplot occupying the first row.
200+
201+
fig = pygmt.Figure()
202+
# Bottom row, two subplots
203+
with fig.subplot(nrows=1, ncols=2, figsize=("15c", "3c"), autolabel="b)"):
204+
fig.basemap(
205+
region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], panel=[0, 0]
206+
)
207+
fig.basemap(
208+
region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], panel=[0, 1]
209+
)
210+
# Move plot origin by 1 cm above the height of the entire figure
211+
fig.shift_origin(yshift="h+1c")
212+
# Top row, one subplot
213+
with fig.subplot(nrows=1, ncols=1, figsize=("15c", "3c"), autolabel="a)"):
214+
fig.basemap(
215+
region=[0, 10, 0, 10], projection="X?", frame=["af", "WSne"], panel=[0, 0]
216+
)
217+
fig.text(text="TEXT", x=5, y=5)
218+
219+
fig.show()
220+
221+
###############################################################################
222+
#
223+
# We start by drawing the bottom two subplots, setting ``autolabel="b)"`` so
224+
# that the subplots are labelled 'b)' and 'c)'. Next, we use
225+
# :meth:`pygmt.Figure.shift_origin` to move the plot origin 1 cm above the
226+
# **h**\ eight of the entire figure that is currently plotted (i.e. the bottom
227+
# row subplots). A single subplot is then plotted on the top row. You may need
228+
# to adjust the ``yshift`` parameter to make your plot look nice. This top row
229+
# uses ``autolabel="a)"``, and we also plotted some text inside. Note that
230+
# ``projection="X?"`` was used to let GMT automatically determine the size of
231+
# the subplot according to the size of the subplot area.
232+
233+
###############################################################################
234+
# You can also manually override the ``autolabel`` for each subplot using for
235+
# example, ``fig.set_panel(..., fixedlabel="b) Panel 2")`` which would allow
236+
# you to manually label a single subplot as you wish. This can be useful for
237+
# adding a more descriptive subtitle to individual subplots.

pygmt/figure.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,5 +387,7 @@ def _repr_html_(self):
387387
meca,
388388
plot,
389389
plot3d,
390+
set_panel,
391+
subplot,
390392
text,
391393
)

pygmt/src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pygmt.src.meca import meca
2525
from pygmt.src.plot import plot
2626
from pygmt.src.plot3d import plot3d
27+
from pygmt.src.subplot import set_panel, subplot
2728
from pygmt.src.surface import surface
2829
from pygmt.src.text import text_ as text # "text" is an argument within "text_"
2930
from pygmt.src.which import which

0 commit comments

Comments
 (0)