Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5752a94

Browse files
committed
ENH: Subfigures
1 parent aa82b50 commit 5752a94

File tree

21 files changed

+2075
-1607
lines changed

21 files changed

+2075
-1607
lines changed

doc/api/figure_api.rst

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,5 @@
55
.. currentmodule:: matplotlib.figure
66

77
.. automodule:: matplotlib.figure
8-
:no-members:
9-
:no-inherited-members:
10-
11-
Classes
12-
-------
13-
14-
.. autosummary::
15-
:toctree: _as_gen/
16-
:template: autosummary.rst
17-
:nosignatures:
18-
19-
Figure
20-
SubplotParams
21-
22-
Functions
23-
---------
24-
25-
.. autosummary::
26-
:toctree: _as_gen/
27-
:template: autosummary.rst
28-
:nosignatures:
29-
30-
figaspect
8+
:members:
9+
:inherited-members:
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Axes3D no longer adds itself to figure
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
New `.Axes3D` objects previously added themselves to figures when they were
5+
created, which lead to them being added twice if
6+
``fig.add_subplot(111, projection='3d')`` was called. Now ``ax = Axes3d(fig)``
7+
will need to be explicitly added to the figure with ``fig.add_axes(ax)``, as
8+
also needs to be done for normal `.axes.Axes`.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FigureBase class added, and Figure class made a child
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The new subfigure feature motivated some re-organization of the
5+
`.figure.Figure` class, so that the new `.Figure.SubFigure` class could have
6+
all the capabilities of a figure.
7+
8+
The `.figure.Figure` class is now a subclass of `.figure.FigureBase`, where
9+
`.figure.FigureBase` contains figure-level artist addition routines, and
10+
the `.figure.Figure` subclass just contains features that are unique to the
11+
outer figure.
12+
13+
Note that there are a new ``.Subfigure.transSubfigure` transform
14+
associated with the subfigure. This transform also exists for a
15+
`.Figure` instance, and is equal to `.Figure.transFigure` in that case,
16+
so code that uses the transform stack that wants to place objects on either
17+
the parent figure or one of the subfigures should use `.Figure.transSubfigure`.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
New subfigure functionality
2+
---------------------------
3+
New `.figure.Figure.add_subfigure` and `.figure.Figure.subfigures`
4+
functionalities allow creating virtual figures within figures. This sort
5+
of nesting was mostly done with nested gridspecs
6+
:doc:`gallery/subplots_axes_and_figures/gridspec_nested `. However, this
7+
did not allow localized figure artists (i.e. a colorbar or suptitle) that
8+
only pertained to each subgridspec.
9+
10+
The new methods `.figure.Figure.add_subfigure` and `.figure.Figure.subfigures`
11+
are meant to rhyme with `.figure.Figure.add_subplot` and
12+
`.figure.Figure.subplots` and have most of the same arguments.
13+
14+
See :doc:`gallery/subplots_axes_and_figures/subfigures`.
15+
16+
.. note::
17+
18+
The subfigure functionality is experimental API as of v3.4.
19+
20+
.. plot::
21+
22+
def example_plot(ax, fontsize=12, hide_labels=False):
23+
pc = ax.pcolormesh(np.random.randn(30, 30))
24+
if not hide_labels:
25+
ax.set_xlabel('x-label', fontsize=fontsize)
26+
ax.set_ylabel('y-label', fontsize=fontsize)
27+
ax.set_title('Title', fontsize=fontsize)
28+
return pc
29+
30+
31+
fig = plt.figure(constrained_layout=True, figsize=(10, 4))
32+
subfigs = fig.subfigures(1, 2, wspace=0.07)
33+
34+
axsLeft = subfigs[0].subplots(1, 2, sharey=True)
35+
subfigs[0].set_facecolor('0.75')
36+
for ax in axsLeft:
37+
pc = example_plot(ax)
38+
subfigs[0].suptitle('Left plots', fontsize='x-large')
39+
subfigs[0].colorbar(pc, shrink=0.6, ax=axsLeft, location='bottom')
40+
41+
axsRight = subfigs[1].subplots(3, 1, sharex=True)
42+
for nn, ax in enumerate(axsRight):
43+
pc = example_plot(ax, hide_labels=True)
44+
if nn == 2:
45+
ax.set_xlabel('xlabel')
46+
if nn == 1:
47+
ax.set_ylabel('ylabel')
48+
subfigs[1].colorbar(pc, shrink=0.6, ax=axsRight)
49+
subfigs[1].suptitle('Right plots', fontsize='x-large')
50+
51+
fig.suptitle('Figure suptitle', fontsize='xx-large')
52+
53+
plt.show()

examples/subplots_axes_and_figures/gridspec_nested.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
GridSpecs can be nested, so that a subplot from a parent GridSpec can
77
set the position for a nested grid of subplots.
88
9+
Note that the same functionality can be achieved more directly with
10+
`~.figure.FigureBase.subfigures`:
11+
:doc:`nested gridspecs</gallery/subplots_axes_and_figures/subpanels>`,
12+
913
"""
1014
import matplotlib.pyplot as plt
1115
import matplotlib.gridspec as gridspec
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
=================
3+
Figure subfigures
4+
=================
5+
6+
Sometimes it is desirable to have a figure with two different layouts in it.
7+
This can be achieved with
8+
:doc:`nested gridspecs</gallery/subplots_axes_and_figures/gridspec_nested>`,
9+
but having a virtual figure with its own artists is helpful, so
10+
Matplotlib also has "subfigures", accessed by calling
11+
`matplotlib.figure.Figure.add_subfigure` in a way that is analagous to
12+
`matplotlib.figure.Figure.add_subplot`, or
13+
`matplotlib.figure.Figure.subfigures` to make an array of subfigures. Note
14+
that subfigures can also have their own child subfigures.
15+
16+
.. note::
17+
``subfigure`` is new in v3.4, and the API is still provisional.
18+
19+
"""
20+
import matplotlib.pyplot as plt
21+
import numpy as np
22+
23+
24+
def example_plot(ax, fontsize=12, hide_labels=False):
25+
pc = ax.pcolormesh(np.random.randn(30, 30))
26+
if not hide_labels:
27+
ax.set_xlabel('x-label', fontsize=fontsize)
28+
ax.set_ylabel('y-label', fontsize=fontsize)
29+
ax.set_title('Title', fontsize=fontsize)
30+
return pc
31+
32+
33+
# gridspec inside gridspec
34+
fig = plt.figure(constrained_layout=True, figsize=(10, 4))
35+
subfigs = fig.subfigures(1, 2, wspace=0.07)
36+
37+
axsLeft = subfigs[0].subplots(1, 2, sharey=True)
38+
subfigs[0].set_facecolor('0.75')
39+
for ax in axsLeft:
40+
pc = example_plot(ax)
41+
subfigs[0].suptitle('Left plots', fontsize='x-large')
42+
subfigs[0].colorbar(pc, shrink=0.6, ax=axsLeft, location='bottom')
43+
44+
axsRight = subfigs[1].subplots(3, 1, sharex=True)
45+
for nn, ax in enumerate(axsRight):
46+
pc = example_plot(ax, hide_labels=True)
47+
if nn == 2:
48+
ax.set_xlabel('xlabel')
49+
if nn == 1:
50+
ax.set_ylabel('ylabel')
51+
subfigs[1].set_facecolor('0.85')
52+
subfigs[1].colorbar(pc, shrink=0.6, ax=axsRight)
53+
subfigs[1].suptitle('Right plots', fontsize='x-large')
54+
55+
fig.suptitle('Figure suptitle', fontsize='xx-large')
56+
57+
plt.show()

examples/subplots_axes_and_figures/subplots_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
# Still there remains an unused empty space between the subplots.
145145
#
146146
# To precisely control the positioning of the subplots, one can explicitly
147-
# create a `.GridSpec` with `.add_gridspec`, and then call its
147+
# create a `.GridSpec` with `.Figure.add_gridspec`, and then call its
148148
# `~.GridSpecBase.subplots` method. For example, we can reduce the height
149149
# between vertical subplots using ``add_gridspec(hspace=0)``.
150150
#

lib/matplotlib/_constrained_layout.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
103103
# larger/smaller). This second reposition tends to be much milder,
104104
# so doing twice makes things work OK.
105105

106-
# make margins for all the axes and subpanels in the
106+
# make margins for all the axes and subfigures in the
107107
# figure. Add margins for colorbars...
108108
_make_layout_margins(fig, renderer, h_pad=h_pad, w_pad=w_pad,
109109
hspace=hspace, wspace=wspace)
@@ -207,8 +207,7 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0,
207207
hspace=hspace, wspace=wspace)
208208
panel._layoutgrid.parent.edit_outer_margin_mins(margins, ss)
209209

210-
# for ax in [a for a in fig._localaxes if hasattr(a, 'get_subplotspec')]:
211-
for ax in fig.get_axes():
210+
for ax in fig._localaxes.as_list():
212211
if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout():
213212
continue
214213

@@ -223,7 +222,6 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0,
223222
hspace=hspace, wspace=wspace)
224223
margin0 = margin.copy()
225224
pos, bbox = _get_pos_and_bbox(ax, renderer)
226-
227225
# the margin is the distance between the bounding box of the axes
228226
# and its position (plus the padding from above)
229227
margin['left'] += pos.x0 - bbox.x0
@@ -281,9 +279,10 @@ def _make_margin_suptitles(fig, renderer, *, w_pad=0, h_pad=0):
281279
# top level figure margin larger.
282280
for panel in fig.panels:
283281
_make_margin_suptitles(panel, renderer, w_pad=w_pad, h_pad=h_pad)
284-
invTransFig = fig.transPanel.inverted().transform_bbox
282+
invTransFig = fig.transSubfigure.inverted().transform_bbox
285283

286-
w_pad, h_pad = (fig.transPanel - fig.transFigure).transform((w_pad, h_pad))
284+
w_pad, h_pad = (fig.transSubfigure -
285+
fig.transFigure).transform((w_pad, h_pad))
287286

288287
if fig._suptitle is not None and fig._suptitle.get_in_layout():
289288
bbox = invTransFig(fig._suptitle.get_tightbbox(renderer))
@@ -429,7 +428,7 @@ def _get_pos_and_bbox(ax, renderer):
429428
fig = ax.figure
430429
pos = ax.get_position(original=True)
431430
# pos is in panel co-ords, but we need in figure for the layout
432-
pos = pos.transformed(fig.transPanel - fig.transFigure)
431+
pos = pos.transformed(fig.transSubfigure - fig.transFigure)
433432
try:
434433
tightbbox = ax.get_tightbbox(renderer=renderer, for_layout_only=True)
435434
except TypeError:
@@ -446,7 +445,7 @@ def _reposition_axes(fig, renderer, *, w_pad=0, h_pad=0, hspace=0, wspace=0):
446445
"""
447446
Reposition all the axes based on the new inner bounding box.
448447
"""
449-
trans_fig_to_panel = fig.transFigure - fig.transPanel
448+
trans_fig_to_panel = fig.transFigure - fig.transSubfigure
450449
for panel in fig.panels:
451450
bbox = panel._layoutgrid.get_outer_bbox()
452451
panel._redo_transform_rel_fig(
@@ -455,9 +454,7 @@ def _reposition_axes(fig, renderer, *, w_pad=0, h_pad=0, hspace=0, wspace=0):
455454
w_pad=w_pad, h_pad=h_pad,
456455
wspace=wspace, hspace=hspace)
457456

458-
# for ax in fig._localaxes:
459-
# if not hasattr(a, 'get_subplotspec'):
460-
for ax in fig.get_axes():
457+
for ax in fig._localaxes.as_list():
461458
if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout():
462459
continue
463460

@@ -512,7 +509,7 @@ def _reposition_colorbar(cbax, renderer, *, offset=None):
512509
gs = parents[0].get_gridspec()
513510
ncols, nrows = gs.ncols, gs.nrows
514511
fig = cbax.figure
515-
trans_fig_to_panel = fig.transFigure - fig.transPanel
512+
trans_fig_to_panel = fig.transFigure - fig.transSubfigure
516513

517514
cb_rspans, cb_cspans = _get_cb_parent_spans(cbax)
518515
bboxparent = gs._layoutgrid.get_bbox_for_cb(rows=cb_rspans, cols=cb_cspans)
@@ -563,7 +560,7 @@ def _reposition_colorbar(cbax, renderer, *, offset=None):
563560
pbcb = pbcb.translated(0, dy)
564561

565562
pbcb = trans_fig_to_panel.transform_bbox(pbcb)
566-
cbax.set_transform(fig.transPanel)
563+
cbax.set_transform(fig.transSubfigure)
567564
cbax._set_position(pbcb)
568565
cbax.set_aspect(aspect, anchor=anchor, adjustable='box')
569566
return offset

lib/matplotlib/axes/_axes.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,31 @@
3939
_log = logging.getLogger(__name__)
4040

4141

42+
class _InsetLocator:
43+
"""
44+
Axes locator for `.Axes.inset_axes`.
45+
46+
The locater is a callable object used in `.Axes.set_aspect` to compute the
47+
axes location depending on the renderer.
48+
"""
49+
50+
def __init__(self, bounds, transform):
51+
"""
52+
*bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together
53+
specify the position of the inset axes.
54+
"""
55+
self._bounds = bounds
56+
self._transform = transform
57+
58+
def __call__(self, ax, renderer):
59+
# Subtracting transSubfigure will typically rely on inverted(),
60+
# freezing the transform; thus, this needs to be delayed until draw
61+
# time as transSubfigure may otherwise change after this is evaluated.
62+
return mtransforms.TransformedBbox(
63+
mtransforms.Bbox.from_bounds(*self._bounds),
64+
self._transform - ax.figure.transSubfigure)
65+
66+
4267
# The axes module contains all the wrappers to plotting functions.
4368
# All the other methods should go in the _AxesBase class.
4469

@@ -449,7 +474,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None,
449474

450475
# decide which two of the lines to keep visible....
451476
pos = inset_ax.get_position()
452-
bboxins = pos.transformed(self.figure.transFigure)
477+
bboxins = pos.transformed(self.figure.transSubfigure)
453478
rectbbox = mtransforms.Bbox.from_bounds(
454479
*bounds
455480
).transformed(transform)

lib/matplotlib/axes/_base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ def set_figure(self, fig):
666666
super().set_figure(fig)
667667

668668
self.bbox = mtransforms.TransformedBbox(self._position,
669-
fig.transFigure)
669+
fig.transSubfigure)
670670
# these will be updated later as data is added
671671
self.dataLim = mtransforms.Bbox.null()
672672
self._viewLim = mtransforms.Bbox.unit()
@@ -1645,8 +1645,10 @@ def apply_aspect(self, position=None):
16451645
self._set_position(position, which='active')
16461646
return
16471647

1648-
fig_width, fig_height = self.get_figure().get_size_inches()
1649-
fig_aspect = fig_height / fig_width
1648+
trans = self.get_figure().transSubfigure
1649+
bb = mtransforms.Bbox.from_bounds(0, 0, 1, 1).transformed(trans)
1650+
# this is the physical aspect of the panel (or figure):
1651+
fig_aspect = bb.height / bb.width
16501652

16511653
if self._adjustable == 'box':
16521654
if self in self._twinned_axes:

0 commit comments

Comments
 (0)