Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental subplot implementation that mimics matplotlib's style #427

Closed
wants to merge 13 commits into from

Conversation

weiji14
Copy link
Member

@weiji14 weiji14 commented May 13, 2020

Description of proposed changes

Experimenting with a more matplotlib-like syntax for managing subplots (an offshoot from #412). Preview of tutorial is at https://pygmt-git-fancysubplots.gmt.now.sh/tutorials/subplots.html, which is actually translated and adapted from the great example at https://docs.gmt-china.org/latest/tutorial/subplot/ (in Chinese). Specifically, this mimics the fig, axs = plt.subplots() style of defining subplots! An example of how it looks like currently:

import pygmt

fig, axs = pygmt.subplots(nrows=1, ncols=2, figsize=("6c", "3c"))
fig.sca(ax=axs[0, 0])
fig.basemap(region=[0, 3, 0, 3], frame=True)
fig.sca(ax=axs[0, 1])
fig.basemap(region=[0, 3, 0, 3], frame=True)
fig.end_subplot()
fig.show()

# or

fig, axs = pygmt.subplots(nrows=1, ncols=2, figsize=("6c", "3c"))
fig.basemap(region=[0, 3, 0, 3], frame=True, ax=axs[0, 0])
fig.basemap(region=[0, 3, 0, 3], frame=True, ax=axs[0, 1])
fig.end_subplot()
fig.show()

produces:

Basic Subplot

The code isn't perfect yet, and I'm very open to suggestions on how to go about improving this implementation. Specifically, it would be cool if we could do ax.plot() like in matplotlib. Calling expert Pythonistas for tips 👋

As it currently stands, there's a high-level subplots function in subplot.py (somewhat based on the matplotlib plt.subplots equivalent at https://github.com/matplotlib/matplotlib/blob/v3.2.1/lib/matplotlib/pyplot.py#L1044). This wraps around the new class SubPlot (inherited from the main Figure class to include subplot functionality). Note that since set is a Python reserved character so I've used sca instead to set the current active subplot, following matplotlib.

Also cross-referencing Julia wrapper/implementation at https://www.generic-mapping-tools.org/GMT.jl/latest/#GMT.subplot.

Parameters/Aliases to wrap:

  • F = dims/dimensions/size/sizes
  • A = autolabel
  • B = frame
  • C = clearance
  • J = projection
  • M = margins
  • R = region
  • S (C/R) = layout
  • T= title

References:

TODO:

  • Initial implementation
  • Alias ax to the -c command in GMT plotting functions
  • Find a way to call ax[0, 1].plot() directly instead of using fig.sca(ax).

Addresses #366 (comment), Supersedes and Closes #412, Fixes #20.

Reminders

  • Run make format and make check to make sure the code follows the style guide.
  • Add tests for new features or tests that would have caught the bug that you're fixing.
  • Add new public functions/methods/classes to doc/api/index.rst.
  • Write detailed docstrings for all functions/methods.
  • If adding new functionality, add an example to docstrings or tutorials.

@weiji14 weiji14 added this to the 0.2.0 milestone May 18, 2020
@weiji14 weiji14 added enhancement Improving an existing feature help wanted Helping hands are appreciated labels May 25, 2020
@leouieda
Copy link
Member

Hi Wei Ji, thanks for doing this! I’ve been looking through this a bit now. I like the subplots function! But I think we shouldn’t do sca as well. A major fault of matplotlib is having too many ways of doing the same thing. We can stick close to their api but need to be careful not to repeat their mistakes.

@weiji14
Copy link
Member Author

weiji14 commented Jun 20, 2020

But I think we shouldn’t do sca as well. A major fault of matplotlib is having too many ways of doing the same thing. We can stick close to their api but need to be careful not to repeat their mistakes.

Yes, the good ol Zen of Python - "There should be one-- and preferably only one --obvious way to do it.". I definitely prefer using ax= to set the current subplot.

However, the sca function (which is basically an alias for GMT subplot's set) would be helpful say, if we want to label our subplots "abcd" or set a clearance/space between subplots. Would there be a better way to do those without resorting to using sca?

Edit: I've started a forum thread at https://forum.generic-mapping-tools.org/t/pygmt-as-a-high-level-api-for-geovisualization/590/2 to discuss this further after PyGMT Meeting #1.

@weiji14 weiji14 marked this pull request as draft June 28, 2020 00:32
@seisman seisman mentioned this pull request Jul 25, 2020
3 tasks
@weiji14 weiji14 modified the milestones: 0.2.x, v0.3.x Sep 7, 2020
Wrapping the `subplot` function! Original GMT `subplot` function can be found at https://docs.generic-mapping-tools.org/latest/subplot.html. Current implementation has a 'directive' statement to tell subplot whether to 'begin', 'set' or 'end' a subplot, and the position/number of row(s) and column(s) can be set too. Also created an alias for dimensions (F).
Include test to check that map frame setting is applied to all subplot figures when using 'begin' subplot directive.
@vercel vercel bot temporarily deployed to Preview September 20, 2020 11:45 Inactive
Translated and adapted from https://github.com/gmt-china/GMT_Docs/blob/master/source/tutorial/subplot.rst. Covers basics of using pygmt subplot begin, set and end. Still need to handle gmt "set" using index instead of just row and col.
Experimenting with a more matplotlib-like syntax for managing subplots, namely the `fig, axs = plt.subplots()` style of defining subplots! A high-level `subplots` function is defined in pygmtplots.py, that wraps around the new class SubPlot (inherited from the main Figure class to include subplot functionality). Re-aliased F to figsize (previously called dimensions). Also updated tests and tutorials to reference this new syntax.
Used to advance to the selected subplot panel. Technically only allowed when in subplot mode. See https://docs.generic-mapping-tools.org/latest/gmt.html#c-full.
@weiji14
Copy link
Member Author

weiji14 commented Sep 20, 2020

Ping @MarkWieczorek, I noticed that SHTOOLS has a tutorial on creating subplots using PyGMT's using X and Y offsets at https://nbviewer.jupyter.org/github/SHTOOLS/SHTOOLS/blob/master/examples/notebooks/plotting-maps.ipynb#Creating-Subplots. Right now this PR is attempting to have a syntax like fig, ax = pygmt.subplots(nrows=1, ncols=2) which could make it easier to create subplots in the future (i.e. without relying on X and Y offsets).

Would be great to get hear your thoughts on this, e.g. would you like to do grid.plotgmt(..., ax=ax[0]) in SHTOOLS? Let us know if there's anything in particular from matplotlib you would like or not like to have implemented here 😁

@MarkWieczorek
Copy link
Contributor

This looks good to me! I would be happy to test this with shtools, but this will have to wait until mid october....

@weiji14 weiji14 changed the base branch from subplot to master October 15, 2020 04:04
Also modified `basemap` to allow no frame (B) when ax (c) is used (i.e. with subplot).
Also added in the `ax=True` tip for auto selection of next subplot, and fixed some typos in previous commit.
Copy link
Member Author

@weiji14 weiji14 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I'm marking this as ready to review, though there are still some rough spots. The translation over from https://docs.gmt-china.org/latest/tutorial/subplot/ is complete, and I think it's worth the team taking a look. Let me know if I should cherry-pick stuff out into separate PRs to make it easier to review.

Note that there are some functionality from https://docs.generic-mapping-tools.org/latest/subplot.html that aren't covered yet:

Comment on lines +203 to +221
###############################################################################
# .. 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.subplots`. 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.

###############################################################################
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can take this out first, if we want to wait for upstream GMT to support nested subplots at GenericMappingTools/gmt#1280.

Comment on lines +185 to +193
fig, axs = pygmt.subplots(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.end_subplot()
fig.show()
Copy link
Member Author

@weiji14 weiji14 Oct 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to fix this example, since it's currently labelling the subplots as 'a)', 'c)', 'd)' (and the 'a)' position is wrong). The obvious way would be to use fig.sca (i.e. subplot set), but if we want to avoid it as per #427 (comment), then it's going to require more thought.

image

Comment on lines +54 to +77
###############################################################################
# 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])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self to remove mention of fig.sca, if we're going to hide this away as per #427 (comment).

@weiji14 weiji14 marked this pull request as ready for review October 15, 2020 10:05
@weiji14 weiji14 added feature Brand new feature and removed enhancement Improving an existing feature labels Oct 15, 2020
@weiji14 weiji14 mentioned this pull request Oct 22, 2020
@weiji14 weiji14 mentioned this pull request Jan 5, 2021
5 tasks
weiji14 added a commit that referenced this pull request Jan 30, 2021
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.
@weiji14 weiji14 mentioned this pull request Jan 30, 2021
21 tasks
weiji14 added a commit that referenced this pull request Jan 30, 2021
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.
@weiji14 weiji14 closed this in #822 Feb 14, 2021
weiji14 added a commit that referenced this pull request Feb 14, 2021
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
#412 and
#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 #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]>
@seisman seisman deleted the fancy_subplots branch March 14, 2021 17:38
sixy6e pushed a commit to sixy6e/pygmt that referenced this pull request Dec 21, 2022
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Brand new feature help wanted Helping hands are appreciated
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement subplot command
3 participants