Skip to content

Commit

Permalink
Wrap subplot - version 3
Browse files Browse the repository at this point in the history
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 my 3rd attempt at implementing `subplot` in PyGMT,
with commits heavily re-adapted/cherry-picked from
#412 and
#427.
  • Loading branch information
weiji14 committed Jan 30, 2021
1 parent ef628bc commit f2ac98b
Show file tree
Hide file tree
Showing 7 changed files with 499 additions and 4 deletions.
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ function-naming-style=snake_case
good-names=i,
j,
k,
ax,
ex,
Run,
_,
Expand Down
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
tutorials/coastlines.rst
tutorials/regions.rst
tutorials/plot.rst
tutorials/subplots.rst
tutorials/lines.rst
tutorials/text.rst
tutorials/contour-map.rst
Expand Down
238 changes: 238 additions & 0 deletions examples/tutorials/subplots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
"""
Subplots
========
When you're preparing a figure for a paper, there will often be times when
you'll need to put many individual plots into one large figure, and label them
'abcd'. These individual plots are called subplots.
There are two main ways to create subplots in GMT:
- Use :meth:`pygmt.Figure.shift_origin` to manually move each individual plot
to the right position.
- Use :meth:`pygmt.Figure.subplot` to define the layout of the subplots.
The first method is easier to use and should handle simple cases involving a
couple of subplots. For more advanced subplot layouts however, we recommend the
use of :meth:`pygmt.Figure.subplot` which offers finer grained control, and
this is what the tutorial below will cover.
"""

###############################################################################
# Let's start by importing the PyGMT library and initializing a Figure

import pygmt

fig = pygmt.Figure()

###############################################################################
# Define subplot layout
# ---------------------
#
# The :meth:`pygmt.Figure.subplot` command is used to setup the layout, size,
# and other attributes of the figure. It divides the whole canvas into regular
# grid areas with n rows and m columns. Each grid area can contain an
# individual subplot. For example:

###############################################################################
# .. code-block:: default
#
# with fig.subplot(
# nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb"
# ) as axs:
# ...

###############################################################################
# will define our figure to have a 2 row and 3 column grid layout.
# ``figsize=("15c", "6c")`` defines the overall size of the figure to be 15cm
# wide by 6cm high. Using ``frame="lrtb"`` allows us to customize the map frame
# for all subplots instead of setting them individually. The figure layout will
# look like the following:

with fig.subplot(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb") as axs:
for index in axs.flatten():
i = index // axs.shape[1] # row
j = index % axs.shape[1] # column
fig.sca(ax=axs[i, j]) # sets the current Axes
fig.text(
position="MC",
text=f"index: {index}, row: {i}, col: {j}",
region=[0, 1, 0, 1],
)
fig.show()

###############################################################################
# The ``fig.sca`` command activates a specified subplot, and all subsequent
# plotting commands will take place in that subplot. This is similar to
# matplotlib's ``plt.sca`` method. In order to specify a subplot, you will need
# to provide the identifier for that subplot via the ``ax`` argument. This can
# be found in the ``axs`` variable referenced by the ``row`` and ``col``
# number.

###############################################################################
# .. note::
#
# The row and column numbering starts from 0. So for a subplot layout with
# N rows and M columns, row numbers will go from 0 to N-1, and column
# numbers will go from 0 to M-1.

###############################################################################
# For example, to activate the subplot on the top right corner (index: 2) at
# ``row=0`` and ``col=2``, so that all subsequent plotting commands happen
# there, you can use the following command:

###############################################################################
# .. code-block:: default
#
# fig.sca(ax=axs[0, 2])

###############################################################################
# Making your first subplot
# -------------------------
# Next, let's use what we learned above to make a 2 row by 2 column subplot
# figure. We'll also pick up on some new parameters to configure our subplot.

fig = pygmt.Figure()
with fig.subplot(
nrows=2,
ncols=2,
figsize=("15c", "6c"),
autolabel=True,
margins=["0.1c", "0.2c"],
title='"My Subplot Heading"',
) as axs:
fig.basemap(
region=[0, 10, 0, 10], projection="X?", frame=["af", "WSne"], ax=axs[0, 0]
)
fig.basemap(
region=[0, 20, 0, 10], projection="X?", frame=["af", "WSne"], ax=axs[0, 1]
)
fig.basemap(
region=[0, 10, 0, 20], projection="X?", frame=["af", "WSne"], ax=axs[1, 0]
)
fig.basemap(
region=[0, 20, 0, 20], projection="X?", frame=["af", "WSne"], ax=axs[1, 1]
)
fig.show()

###############################################################################
# In this example, we define a 2-row, 2-column (2x2) subplot layout using
# :meth:`pygmt.Figure.subplot`. The overall figure dimensions is set to be 15cm
# wide and 6cm high (``figsize=["15c", "6c"]``). In addition, we used some
# optional parameters to fine tune some details of the figure creation:
#
# - ``autolabel=True``: Each subplot is automatically labelled abcd
# - ``margins=["0.1c", "0.2c"]``: adjusts the space between adjacent subplots.
# In this case, it is set as 0.1 cm in the X direction and 0.2 cm in the Y
# direction.
# - ``title="My Subplot Heading"``: adds a title on top of the whole figure.
#
# Notice that each subplot was set to use a linear projection ``"X?"``.
# Usually, we need to specify the width and height of the map frame, but it is
# also possible to use a question mark ``"?"`` to let GMT decide automatically
# on what is the most appropriate width/height for the each subplot's map
# frame.

###############################################################################
# .. tip::
#
# In the above example, we used the following commands to activate the
# four subplots explicitly one after another::
#
# fig.basemap(..., ax=axs[0, 0])
# fig.basemap(..., ax=axs[0, 1])
# fig.basemap(..., ax=axs[1, 0])
# fig.basemap(..., ax=axs[1, 1])
#
# In fact, we can just use ``fig.basemap(..., ax=True)`` without specifying
# any subplot index number, and GMT will automatically activate the next
# subplot.

###############################################################################
# Shared X and Y axis labels
# --------------------------
# In the example above with the four subplots, the two subplots for each row
# have the same Y-axis range, and the two subplots for each column have the
# same X-axis range. You can use the **layout** option to set a common X and/or
# Y axis between subplots.

fig = pygmt.Figure()
with fig.subplot(
nrows=2,
ncols=2,
figsize=("15c", "6c"),
autolabel=True,
margins=["0.3c", "0.2c"],
title='"My Subplot Heading"',
layout=["Rl", "Cb"],
frame="WSrt",
) as axs:
fig.basemap(region=[0, 10, 0, 10], projection="X?", ax=True)
fig.basemap(region=[0, 20, 0, 10], projection="X?", ax=True)
fig.basemap(region=[0, 10, 0, 20], projection="X?", ax=True)
fig.basemap(region=[0, 20, 0, 20], projection="X?", ax=True)
fig.show()

###############################################################################
# **Rl** indicates that subplots within a **R**\ ow will share the y-axis, and
# only the **l**\ eft axis is displayed. **Cb** indicates that subplots in
# a column will share the x-axis, and only the **b**\ ottom axis is displayed.
#
# Of course, instead of using the **layout** option, you can also set a
# different **frame** for each subplot to control the axis properties
# individually for each subplot.

###############################################################################
# Advanced subplot layouts
# ------------------------
#
# Nested subplot are currently not supported. If you want to create more
# complex subplot layouts, some manual adjustments are needed.
#
# The following example draws three subplots in a 2-row, 2-column layout, with
# the first subplot occupying the first row.

fig = pygmt.Figure()
with fig.subplot(nrows=2, ncols=2, figsize=("15c", "6c"), autolabel=True):
fig.basemap(
region=[0, 10, 0, 10], projection="X15c/3c", frame=["af", "WSne"], ax=axs[0, 0]
)
fig.text(text="TEXT", x=5, y=5, projection="X15c/3c")
fig.basemap(
region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], ax=axs[1, 0]
)
fig.basemap(
region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], ax=axs[1, 1]
)
fig.show()

###############################################################################
#
# When drawing the three basemaps, the last two basemaps use
# ``projection="X?"``, so GMT will automatically determine the size of the
# subplot according to the size of the subplot area. In order for the first
# subplot to fill up the entire top row space, we use manually adjusted the
# subplot width to 15cm using ``projection="X15c/3c"``.

###############################################################################
# .. note::
#
# There are bugs that have not been fixed in the above example.
#
# In subplot mode, the size of each subgraph is controlled by the
# ``figsize`` option of :meth:`pygmt.Figure.subplot`. Users can override
# this and use``projection`` to specify the size of an individual subplot,
# but this size will not be remembered. If the next command does not
# specify``projection``, the default size of the subplot mode will be used,
# and the resulting plot will be inccorect.
#
# The current workaround is to use the same ``projection`` option in all
# commands for the subplot. For example, we forced subplot (a) to have a
# different size using ``projection="15c/3c``. The next command within the
# subplot (e.g. ``text``) must also use ``projection="x15c/3c"``, otherwise
# the placement will be wrong.

###############################################################################
# Since we skipped the second subplot, the auto label function will name the
# three subplots as a, c and d, which is not what we want, so we have to use
# ``fig.sca(A=""(a)"`` to manually set the subplot label.
9 changes: 5 additions & 4 deletions pygmt/base_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,7 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs):
V="verbose",
X="xshift",
Y="yshift",
c="ax",
p="perspective",
t="transparency",
)
Expand Down Expand Up @@ -1265,9 +1266,9 @@ def basemap(self, **kwargs):
{t}
"""
kwargs = self._preprocess(**kwargs)
if not args_in_kwargs(args=["B", "L", "Td", "Tm"], kwargs=kwargs):
if not args_in_kwargs(args=["B", "L", "Td", "Tm", "c"], kwargs=kwargs):
raise GMTInvalidInput(
"At least one of frame, map_scale, compass, or rose must be specified."
"At least one of frame, map_scale, compass, rose, or ax must be specified."
)
with Session() as lib:
lib.call_module("basemap", build_arg_string(kwargs))
Expand Down Expand Up @@ -1642,5 +1643,5 @@ 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
# GMT plotting modules
from pygmt.src import meca, sca, subplot # 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 @@ -3,3 +3,4 @@
"""
# pylint: disable=import-outside-toplevel
from pygmt.src.meca import meca
from pygmt.src.subplot import sca, subplot
Loading

0 comments on commit f2ac98b

Please sign in to comment.