matplotlib & pandas

This commit is contained in:
estomm
2020-09-26 22:03:11 +08:00
parent 73cc328c81
commit d31be4f219
599 changed files with 99925 additions and 0 deletions

View File

@@ -0,0 +1,830 @@
---
sidebarDepth: 3
sidebar: auto
---
# Artist tutorial
Using Artist objects to render on the canvas.
There are three layers to the matplotlib API.
- the ``matplotlib.backend_bases.FigureCanvas`` is the area onto which
the figure is drawn
- the ``matplotlib.backend_bases.Renderer`` is
the object which knows how to draw on the
``FigureCanvas``
- and the [``matplotlib.artist.Artist``](https://matplotlib.orgapi/artist_api.html#matplotlib.artist.Artist) is the object that knows how to use
a renderer to paint onto the canvas.
The ``FigureCanvas`` and
``Renderer`` handle all the details of
talking to user interface toolkits like [wxPython](https://www.wxpython.org) or drawing languages like PostScript®, and
the ``Artist`` handles all the high level constructs like representing
and laying out the figure, text, and lines. The typical user will
spend 95% of their time working with the ``Artists``.
There are two types of ``Artists``: primitives and containers. The primitives
represent the standard graphical objects we want to paint onto our canvas:
[``Line2D``](https://matplotlib.orgapi/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D), [``Rectangle``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Rectangle.html#matplotlib.patches.Rectangle),
[``Text``](https://matplotlib.orgapi/text_api.html#matplotlib.text.Text), [``AxesImage``](https://matplotlib.orgapi/image_api.html#matplotlib.image.AxesImage), etc., and
the containers are places to put them ([``Axis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.Axis),
[``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) and [``Figure``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure)). The
standard use is to create a [``Figure``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure) instance, use
the ``Figure`` to create one or more [``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) or
``Subplot`` instances, and use the ``Axes`` instance
helper methods to create the primitives. In the example below, we create a
``Figure`` instance using [``matplotlib.pyplot.figure()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure), which is a
convenience method for instantiating ``Figure`` instances and connecting them
with your user interface or drawing toolkit ``FigureCanvas``. As we will
discuss below, this is not necessary -- you can work directly with PostScript,
PDF Gtk+, or wxPython ``FigureCanvas`` instances, instantiate your ``Figures``
directly and connect them yourselves -- but since we are focusing here on the
``Artist`` API we'll let [``pyplot``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.html#module-matplotlib.pyplot) handle some of those details
for us:
``` python
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1) # two rows, one column, first plot
```
The [``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) is probably the most important
class in the matplotlib API, and the one you will be working with most
of the time. This is because the ``Axes`` is the plotting area into
which most of the objects go, and the ``Axes`` has many special helper
methods ([``plot()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.plot.html#matplotlib.axes.Axes.plot),
[``text()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.text.html#matplotlib.axes.Axes.text),
[``hist()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.hist.html#matplotlib.axes.Axes.hist),
[``imshow()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.imshow.html#matplotlib.axes.Axes.imshow)) to create the most common
graphics primitives ([``Line2D``](https://matplotlib.orgapi/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D),
[``Text``](https://matplotlib.orgapi/text_api.html#matplotlib.text.Text),
[``Rectangle``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Rectangle.html#matplotlib.patches.Rectangle),
``Image``, respectively). These helper methods
will take your data (e.g., ``numpy`` arrays and strings) and create
primitive ``Artist`` instances as needed (e.g., ``Line2D``), add them to
the relevant containers, and draw them when requested. Most of you
are probably familiar with the ``Subplot``,
which is just a special case of an ``Axes`` that lives on a regular
rows by columns grid of ``Subplot`` instances. If you want to create
an ``Axes`` at an arbitrary location, simply use the
[``add_axes()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_axes) method which takes a list
of ``[left, bottom, width, height]`` values in 0-1 relative figure
coordinates:
``` python
fig2 = plt.figure()
ax2 = fig2.add_axes([0.15, 0.1, 0.7, 0.3])
```
Continuing with our example:
``` python
import numpy as np
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2*np.pi*t)
line, = ax.plot(t, s, color='blue', lw=2)
```
In this example, ``ax`` is the ``Axes`` instance created by the
``fig.add_subplot`` call above (remember ``Subplot`` is just a
subclass of ``Axes``) and when you call ``ax.plot``, it creates a
``Line2D`` instance and adds it to the ``Axes.lines`` list. In the interactive [ipython](http://ipython.org/) session below, you can see that the
``Axes.lines`` list is length one and contains the same line that was
returned by the ``line, = ax.plot...`` call:
``` python
In [101]: ax.lines[0]
Out[101]: <matplotlib.lines.Line2D instance at 0x19a95710>
In [102]: line
Out[102]: <matplotlib.lines.Line2D instance at 0x19a95710>
```
If you make subsequent calls to ``ax.plot`` (and the hold state is "on"
which is the default) then additional lines will be added to the list.
You can remove lines later simply by calling the list methods; either
of these will work:
``` python
del ax.lines[0]
ax.lines.remove(line) # one or the other, not both!
```
The Axes also has helper methods to configure and decorate the x-axis
and y-axis tick, tick labels and axis labels:
``` python
xtext = ax.set_xlabel('my xdata') # returns a Text instance
ytext = ax.set_ylabel('my ydata')
```
When you call [``ax.set_xlabel``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.set_xlabel.html#matplotlib.axes.Axes.set_xlabel),
it passes the information on the [``Text``](https://matplotlib.orgapi/text_api.html#matplotlib.text.Text)
instance of the [``XAxis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.XAxis). Each ``Axes``
instance contains an [``XAxis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.XAxis) and a
[``YAxis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.YAxis) instance, which handle the layout and
drawing of the ticks, tick labels and axis labels.
Try creating the figure below.
``` python
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
fig.subplots_adjust(top=0.8)
ax1 = fig.add_subplot(211)
ax1.set_ylabel('volts')
ax1.set_title('a sine wave')
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2*np.pi*t)
line, = ax1.plot(t, s, color='blue', lw=2)
# Fixing random state for reproducibility
np.random.seed(19680801)
ax2 = fig.add_axes([0.15, 0.1, 0.7, 0.3])
n, bins, patches = ax2.hist(np.random.randn(1000), 50,
facecolor='yellow', edgecolor='yellow')
ax2.set_xlabel('time (s)')
plt.show()
```
![sphx_glr_artists_001](https://matplotlib.org/_images/sphx_glr_artists_001.png)
## Customizing your objects
Every element in the figure is represented by a matplotlib
[``Artist``](https://matplotlib.orgapi/artist_api.html#matplotlib.artist.Artist), and each has an extensive list of
properties to configure its appearance. The figure itself contains a
[``Rectangle``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Rectangle.html#matplotlib.patches.Rectangle) exactly the size of the figure,
which you can use to set the background color and transparency of the
figures. Likewise, each [``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) bounding box
(the standard white box with black edges in the typical matplotlib
plot, has a ``Rectangle`` instance that determines the color,
transparency, and other properties of the Axes. These instances are
stored as member variables ``Figure.patch`` and ``Axes.patch`` ("Patch" is a name inherited from
MATLAB, and is a 2D "patch" of color on the figure, e.g., rectangles,
circles and polygons). Every matplotlib ``Artist`` has the following
properties
---
Property
Description
alpha
The transparency - a scalar from 0-1
animated
A boolean that is used to facilitate animated drawing
axes
The axes that the Artist lives in, possibly None
clip_box
The bounding box that clips the Artist
clip_on
Whether clipping is enabled
clip_path
The path the artist is clipped to
contains
A picking function to test whether the artist contains the pick point
figure
The figure instance the artist lives in, possibly None
label
A text label (e.g., for auto-labeling)
picker
A python object that controls object picking
transform
The transformation
visible
A boolean whether the artist should be drawn
zorder
A number which determines the drawing order
rasterized
Boolean; Turns vectors into raster graphics (for compression & eps transparency)
Each of the properties is accessed with an old-fashioned setter or
getter (yes we know this irritates Pythonistas and we plan to support
direct access via properties or traits but it hasn't been done yet).
For example, to multiply the current alpha by a half:
``` python
a = o.get_alpha()
o.set_alpha(0.5*a)
```
If you want to set a number of properties at once, you can also use
the ``set`` method with keyword arguments. For example:
``` python
o.set(alpha=0.5, zorder=2)
```
If you are working interactively at the python shell, a handy way to
inspect the ``Artist`` properties is to use the
[``matplotlib.artist.getp()``](https://matplotlib.orgapi/_as_gen/matplotlib.artist.getp.html#matplotlib.artist.getp) function (simply
``getp()`` in pyplot), which lists the properties
and their values. This works for classes derived from ``Artist`` as
well, e.g., ``Figure`` and ``Rectangle``. Here are the ``Figure`` rectangle
properties mentioned above:
``` python
In [149]: matplotlib.artist.getp(fig.patch)
alpha = 1.0
animated = False
antialiased or aa = True
axes = None
clip_box = None
clip_on = False
clip_path = None
contains = None
edgecolor or ec = w
facecolor or fc = 0.75
figure = Figure(8.125x6.125)
fill = 1
hatch = None
height = 1
label =
linewidth or lw = 1.0
picker = None
transform = <Affine object at 0x134cca84>
verts = ((0, 0), (0, 1), (1, 1), (1, 0))
visible = True
width = 1
window_extent = <Bbox object at 0x134acbcc>
x = 0
y = 0
zorder = 1
```
The docstrings for all of the classes also contain the ``Artist``
properties, so you can consult the interactive "help" or the
[matplotlib.artist](https://matplotlib.orgapi/artist_api.html#artist-api) for a listing of properties for a given object.
## Object containers
Now that we know how to inspect and set the properties of a given
object we want to configure, we need to know how to get at that object.
As mentioned in the introduction, there are two kinds of objects:
primitives and containers. The primitives are usually the things you
want to configure (the font of a [``Text``](https://matplotlib.orgapi/text_api.html#matplotlib.text.Text)
instance, the width of a [``Line2D``](https://matplotlib.orgapi/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D)) although
the containers also have some properties as well -- for example the
[``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) [``Artist``](https://matplotlib.orgapi/artist_api.html#matplotlib.artist.Artist) is a
container that contains many of the primitives in your plot, but it
also has properties like the ``xscale`` to control whether the xaxis
is 'linear' or 'log'. In this section we'll review where the various
container objects store the ``Artists`` that you want to get at.
### Figure container
The top level container ``Artist`` is the
[``matplotlib.figure.Figure``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure), and it contains everything in the
figure. The background of the figure is a
[``Rectangle``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Rectangle.html#matplotlib.patches.Rectangle) which is stored in
``Figure.patch``. As
you add subplots ([``add_subplot()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_subplot)) and
axes ([``add_axes()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_axes)) to the figure
these will be appended to the [``Figure.axes``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.axes). These are also returned by the
methods that create them:
``` python
In [156]: fig = plt.figure()
In [157]: ax1 = fig.add_subplot(211)
In [158]: ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3])
In [159]: ax1
Out[159]: <matplotlib.axes.Subplot instance at 0xd54b26c>
In [160]: print(fig.axes)
[<matplotlib.axes.Subplot instance at 0xd54b26c>, <matplotlib.axes.Axes instance at 0xd3f0b2c>]
```
Because the figure maintains the concept of the "current axes" (see
[``Figure.gca``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.gca) and
[``Figure.sca``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.sca)) to support the
pylab/pyplot state machine, you should not insert or remove axes
directly from the axes list, but rather use the
[``add_subplot()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_subplot) and
[``add_axes()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_axes) methods to insert, and the
[``delaxes()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.delaxes) method to delete. You are
free however, to iterate over the list of axes or index into it to get
access to ``Axes`` instances you want to customize. Here is an
example which turns all the axes grids on:
``` python
for ax in fig.axes:
ax.grid(True)
```
The figure also has its own text, lines, patches and images, which you
can use to add primitives directly. The default coordinate system for
the ``Figure`` will simply be in pixels (which is not usually what you
want) but you can control this by setting the transform property of
the ``Artist`` you are adding to the figure.
More useful is "figure coordinates" where (0, 0) is the bottom-left of
the figure and (1, 1) is the top-right of the figure which you can
obtain by setting the ``Artist`` transform to ``fig.transFigure``:
``` python
import matplotlib.lines as lines
fig = plt.figure()
l1 = lines.Line2D([0, 1], [0, 1], transform=fig.transFigure, figure=fig)
l2 = lines.Line2D([0, 1], [1, 0], transform=fig.transFigure, figure=fig)
fig.lines.extend([l1, l2])
plt.show()
```
![sphx_glr_artists_002](https://matplotlib.org/_images/sphx_glr_artists_002.png)
Here is a summary of the Artists the figure contains
---
Figure attribute
Description
axes
A list of Axes instances (includes Subplot)
patch
The Rectangle background
images
A list of FigureImages patches - useful for raw pixel display
legends
A list of Figure Legend instances (different from Axes.legends)
lines
A list of Figure Line2D instances (rarely used, see Axes.lines)
patches
A list of Figure patches (rarely used, see Axes.patches)
texts
A list Figure Text instances
### Axes container
The [``matplotlib.axes.Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) is the center of the matplotlib
universe -- it contains the vast majority of all the ``Artists`` used
in a figure with many helper methods to create and add these
``Artists`` to itself, as well as helper methods to access and
customize the ``Artists`` it contains. Like the
[``Figure``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure), it contains a
[``Patch``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Patch.html#matplotlib.patches.Patch)
``patch`` which is a
[``Rectangle``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Rectangle.html#matplotlib.patches.Rectangle) for Cartesian coordinates and a
[``Circle``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Circle.html#matplotlib.patches.Circle) for polar coordinates; this patch
determines the shape, background and border of the plotting region:
``` python
ax = fig.add_subplot(111)
rect = ax.patch # a Rectangle instance
rect.set_facecolor('green')
```
When you call a plotting method, e.g., the canonical
[``plot()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.plot.html#matplotlib.axes.Axes.plot) and pass in arrays or lists of
values, the method will create a [``matplotlib.lines.Line2D()``](https://matplotlib.orgapi/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D)
instance, update the line with all the ``Line2D`` properties passed as
keyword arguments, add the line to the ``Axes.lines`` container, and returns it to you:
``` python
In [213]: x, y = np.random.rand(2, 100)
In [214]: line, = ax.plot(x, y, '-', color='blue', linewidth=2)
```
``plot`` returns a list of lines because you can pass in multiple x, y
pairs to plot, and we are unpacking the first element of the length
one list into the line variable. The line has been added to the
``Axes.lines`` list:
``` python
In [229]: print(ax.lines)
[<matplotlib.lines.Line2D instance at 0xd378b0c>]
```
Similarly, methods that create patches, like
[``bar()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.bar.html#matplotlib.axes.Axes.bar) creates a list of rectangles, will
add the patches to the ``Axes.patches`` list:
``` python
In [233]: n, bins, rectangles = ax.hist(np.random.randn(1000), 50, facecolor='yellow')
In [234]: rectangles
Out[234]: <a list of 50 Patch objects>
In [235]: print(len(ax.patches))
```
You should not add objects directly to the ``Axes.lines`` or
``Axes.patches`` lists unless you know exactly what you are doing,
because the ``Axes`` needs to do a few things when it creates and adds
an object. It sets the figure and axes property of the ``Artist``, as
well as the default ``Axes`` transformation (unless a transformation
is set). It also inspects the data contained in the ``Artist`` to
update the data structures controlling auto-scaling, so that the view
limits can be adjusted to contain the plotted data. You can,
nonetheless, create objects yourself and add them directly to the
``Axes`` using helper methods like
[``add_line()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.add_line.html#matplotlib.axes.Axes.add_line) and
[``add_patch()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.add_patch.html#matplotlib.axes.Axes.add_patch). Here is an annotated
interactive session illustrating what is going on:
``` python
In [262]: fig, ax = plt.subplots()
# create a rectangle instance
In [263]: rect = matplotlib.patches.Rectangle( (1,1), width=5, height=12)
# by default the axes instance is None
In [264]: print(rect.get_axes())
None
# and the transformation instance is set to the "identity transform"
In [265]: print(rect.get_transform())
<Affine object at 0x13695544>
# now we add the Rectangle to the Axes
In [266]: ax.add_patch(rect)
# and notice that the ax.add_patch method has set the axes
# instance
In [267]: print(rect.get_axes())
Axes(0.125,0.1;0.775x0.8)
# and the transformation has been set too
In [268]: print(rect.get_transform())
<Affine object at 0x15009ca4>
# the default axes transformation is ax.transData
In [269]: print(ax.transData)
<Affine object at 0x15009ca4>
# notice that the xlimits of the Axes have not been changed
In [270]: print(ax.get_xlim())
(0.0, 1.0)
# but the data limits have been updated to encompass the rectangle
In [271]: print(ax.dataLim.bounds)
(1.0, 1.0, 5.0, 12.0)
# we can manually invoke the auto-scaling machinery
In [272]: ax.autoscale_view()
# and now the xlim are updated to encompass the rectangle
In [273]: print(ax.get_xlim())
(1.0, 6.0)
# we have to manually force a figure draw
In [274]: ax.figure.canvas.draw()
```
There are many, many ``Axes`` helper methods for creating primitive
``Artists`` and adding them to their respective containers. The table
below summarizes a small sampling of them, the kinds of ``Artist`` they
create, and where they store them
---
Helper method
Artist
Container
ax.annotate - text annotations
Annotate
ax.texts
ax.bar - bar charts
Rectangle
ax.patches
ax.errorbar - error bar plots
Line2D and Rectangle
ax.lines and ax.patches
ax.fill - shared area
Polygon
ax.patches
ax.hist - histograms
Rectangle
ax.patches
ax.imshow - image data
AxesImage
ax.images
ax.legend - axes legends
Legend
ax.legends
ax.plot - xy plots
Line2D
ax.lines
ax.scatter - scatter charts
PolygonCollection
ax.collections
ax.text - text
Text
ax.texts
In addition to all of these ``Artists``, the ``Axes`` contains two
important ``Artist`` containers: the [``XAxis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.XAxis)
and [``YAxis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.YAxis), which handle the drawing of the
ticks and labels. These are stored as instance variables
``xaxis`` and
``yaxis``. The ``XAxis`` and ``YAxis``
containers will be detailed below, but note that the ``Axes`` contains
many helper methods which forward calls on to the
[``Axis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.Axis) instances so you often do not need to
work with them directly unless you want to. For example, you can set
the font color of the ``XAxis`` ticklabels using the ``Axes`` helper
method:
``` python
for label in ax.get_xticklabels():
label.set_color('orange')
```
Below is a summary of the Artists that the Axes contains
---
Axes attribute
Description
artists
A list of Artist instances
patch
Rectangle instance for Axes background
collections
A list of Collection instances
images
A list of AxesImage
legends
A list of Legend instances
lines
A list of Line2D instances
patches
A list of Patch instances
texts
A list of Text instances
xaxis
matplotlib.axis.XAxis instance
yaxis
matplotlib.axis.YAxis instance
### Axis containers
The [``matplotlib.axis.Axis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.Axis) instances handle the drawing of the
tick lines, the grid lines, the tick labels and the axis label. You
can configure the left and right ticks separately for the y-axis, and
the upper and lower ticks separately for the x-axis. The ``Axis``
also stores the data and view intervals used in auto-scaling, panning
and zooming, as well as the [``Locator``](https://matplotlib.orgapi/ticker_api.html#matplotlib.ticker.Locator) and
[``Formatter``](https://matplotlib.orgapi/ticker_api.html#matplotlib.ticker.Formatter) instances which control where
the ticks are placed and how they are represented as strings.
Each ``Axis`` object contains a ``label`` attribute
(this is what [``pyplot``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.html#module-matplotlib.pyplot) modifies in calls to
[``xlabel()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.xlabel.html#matplotlib.pyplot.xlabel) and [``ylabel()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.ylabel.html#matplotlib.pyplot.ylabel)) as
well as a list of major and minor ticks. The ticks are
[``XTick``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.XTick) and [``YTick``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.YTick) instances,
which contain the actual line and text primitives that render the ticks and
ticklabels. Because the ticks are dynamically created as needed (e.g., when
panning and zooming), you should access the lists of major and minor ticks
through their accessor methods [``get_major_ticks()``](https://matplotlib.orgapi/_as_gen/matplotlib.axis.Axis.get_major_ticks.html#matplotlib.axis.Axis.get_major_ticks)
and [``get_minor_ticks()``](https://matplotlib.orgapi/_as_gen/matplotlib.axis.Axis.get_minor_ticks.html#matplotlib.axis.Axis.get_minor_ticks). Although the ticks contain
all the primitives and will be covered below, ``Axis`` instances have accessor
methods that return the tick lines, tick labels, tick locations etc.:
``` python
fig, ax = plt.subplots()
axis = ax.xaxis
axis.get_ticklocs()
```
![sphx_glr_artists_003](https://matplotlib.org/_images/sphx_glr_artists_003.png)
``` python
axis.get_ticklabels()
```
note there are twice as many ticklines as labels because by
``` python
axis.get_ticklines()
```
by default you get the major ticks back
``` python
axis.get_ticklines()
```
but you can also ask for the minor ticks
``` python
axis.get_ticklines(minor=True)
# Here is a summary of some of the useful accessor methods of the ``Axis``
# (these have corresponding setters where useful, such as
# set_major_formatter)
#
# ====================== =========================================================
# Accessor method Description
# ====================== =========================================================
# get_scale The scale of the axis, e.g., 'log' or 'linear'
# get_view_interval The interval instance of the axis view limits
# get_data_interval The interval instance of the axis data limits
# get_gridlines A list of grid lines for the Axis
# get_label The axis label - a Text instance
# get_ticklabels A list of Text instances - keyword minor=True|False
# get_ticklines A list of Line2D instances - keyword minor=True|False
# get_ticklocs A list of Tick locations - keyword minor=True|False
# get_major_locator The matplotlib.ticker.Locator instance for major ticks
# get_major_formatter The matplotlib.ticker.Formatter instance for major ticks
# get_minor_locator The matplotlib.ticker.Locator instance for minor ticks
# get_minor_formatter The matplotlib.ticker.Formatter instance for minor ticks
# get_major_ticks A list of Tick instances for major ticks
# get_minor_ticks A list of Tick instances for minor ticks
# grid Turn the grid on or off for the major or minor ticks
# ====================== =========================================================
#
# Here is an example, not recommended for its beauty, which customizes
# the axes and tick properties
# plt.figure creates a matplotlib.figure.Figure instance
fig = plt.figure()
rect = fig.patch # a rectangle instance
rect.set_facecolor('lightgoldenrodyellow')
ax1 = fig.add_axes([0.1, 0.3, 0.4, 0.4])
rect = ax1.patch
rect.set_facecolor('lightslategray')
for label in ax1.xaxis.get_ticklabels():
# label is a Text instance
label.set_color('red')
label.set_rotation(45)
label.set_fontsize(16)
for line in ax1.yaxis.get_ticklines():
# line is a Line2D instance
line.set_color('green')
line.set_markersize(25)
line.set_markeredgewidth(3)
plt.show()
```
![sphx_glr_artists_004](https://matplotlib.org/_images/sphx_glr_artists_004.png)
### Tick containers
The [``matplotlib.axis.Tick``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.Tick) is the final container object in our
descent from the [``Figure``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure) to the
[``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) to the [``Axis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.Axis)
to the [``Tick``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.Tick). The ``Tick`` contains the tick
and grid line instances, as well as the label instances for the upper
and lower ticks. Each of these is accessible directly as an attribute
of the ``Tick``.
---
Tick attribute
Description
tick1line
Line2D instance
tick2line
Line2D instance
gridline
Line2D instance
label1
Text instance
label2
Text instance
Here is an example which sets the formatter for the right side ticks with
dollar signs and colors them green on the right side of the yaxis
``` python
import matplotlib.ticker as ticker
# Fixing random state for reproducibility
np.random.seed(19680801)
fig, ax = plt.subplots()
ax.plot(100*np.random.rand(20))
formatter = ticker.FormatStrFormatter('$%1.2f')
ax.yaxis.set_major_formatter(formatter)
for tick in ax.yaxis.get_major_ticks():
tick.label1.set_visible(False)
tick.label2.set_visible(True)
tick.label2.set_color('green')
plt.show()
```
![sphx_glr_artists_005](https://matplotlib.org/_images/sphx_glr_artists_005.png)
## Download
- [Download Python source code: artists.py](https://matplotlib.org/_downloads/a7b58a13e5ee2b59b31d49c2baa9f139/artists.py)
- [Download Jupyter notebook: artists.ipynb](https://matplotlib.org/_downloads/c328d03dcd3b9dae9a8b3f008c82073b/artists.ipynb)

View File

@@ -0,0 +1,148 @@
---
sidebarDepth: 3
sidebar: auto
---
# Styling with cycler
Demo of custom property-cycle settings to control colors and other style
properties for multi-line plots.
::: tip Note
More complete documentation of the ``cycler`` API can be found
[here](http://matplotlib.org/cycler/).
:::
This example demonstrates two different APIs:
1. Setting the default rc parameter specifying the property cycle.
This affects all subsequent axes (but not axes already created).
1. Setting the property cycle for a single pair of axes.
``` python
from cycler import cycler
import numpy as np
import matplotlib.pyplot as plt
```
First we'll generate some sample data, in this case, four offset sine
curves.
``` python
x = np.linspace(0, 2 * np.pi, 50)
offsets = np.linspace(0, 2 * np.pi, 4, endpoint=False)
yy = np.transpose([np.sin(x + phi) for phi in offsets])
```
Now ``yy`` has shape
``` python
print(yy.shape)
```
Out:
```
(50, 4)
```
So ``yy[:, i]`` will give you the ``i``-th offset sine curve. Let's set the
default ``prop_cycle`` using [``matplotlib.pyplot.rc()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.rc.html#matplotlib.pyplot.rc). We'll combine a
color cycler and a linestyle cycler by adding (``+``) two ``cycler``'s
together. See the bottom of this tutorial for more information about
combining different cyclers.
``` python
default_cycler = (cycler(color=['r', 'g', 'b', 'y']) +
cycler(linestyle=['-', '--', ':', '-.']))
plt.rc('lines', linewidth=4)
plt.rc('axes', prop_cycle=default_cycler)
```
Now we'll generate a figure with two axes, one on top of the other. On the
first axis, we'll plot with the default cycler. On the second axis, we'll
set the ``prop_cycle`` using [``matplotlib.axes.Axes.set_prop_cycle()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.set_prop_cycle.html#matplotlib.axes.Axes.set_prop_cycle),
which will only set the ``prop_cycle`` for this [``matplotlib.axes.Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes)
instance. We'll use a second ``cycler`` that combines a color cycler and a
linewidth cycler.
``` python
custom_cycler = (cycler(color=['c', 'm', 'y', 'k']) +
cycler(lw=[1, 2, 3, 4]))
fig, (ax0, ax1) = plt.subplots(nrows=2)
ax0.plot(yy)
ax0.set_title('Set default color cycle to rgby')
ax1.set_prop_cycle(custom_cycler)
ax1.plot(yy)
ax1.set_title('Set axes color cycle to cmyk')
# Add a bit more space between the two plots.
fig.subplots_adjust(hspace=0.3)
plt.show()
```
![sphx_glr_color_cycle_001](https://matplotlib.org/_images/sphx_glr_color_cycle_001.png)
## Setting ``prop_cycle`` in the ``matplotlibrc`` file or style files
Remember, if you want to set a custom cycler in your
``.matplotlibrc`` file or a style file (``style.mplstyle``), you can set the
``axes.prop_cycle`` property:
``` python
axes.prop_cycle : cycler(color='bgrcmyk')
```
## Cycling through multiple properties
You can add cyclers:
``` python
from cycler import cycler
cc = (cycler(color=list('rgb')) +
cycler(linestyle=['-', '--', '-.']))
for d in cc:
print(d)
```
Results in:
``` python
{'color': 'r', 'linestyle': '-'}
{'color': 'g', 'linestyle': '--'}
{'color': 'b', 'linestyle': '-.'}
```
You can multiply cyclers:
``` python
from cycler import cycler
cc = (cycler(color=list('rgb')) *
cycler(linestyle=['-', '--', '-.']))
for d in cc:
print(d)
```
Results in:
``` python
{'color': 'r', 'linestyle': '-'}
{'color': 'r', 'linestyle': '--'}
{'color': 'r', 'linestyle': '-.'}
{'color': 'g', 'linestyle': '-'}
{'color': 'g', 'linestyle': '--'}
{'color': 'g', 'linestyle': '-.'}
{'color': 'b', 'linestyle': '-'}
{'color': 'b', 'linestyle': '--'}
{'color': 'b', 'linestyle': '-.'}
```
## Download
- [Download Python source code: color_cycle.py](https://matplotlib.org/_downloads/6d214f31d57999a93c8a6e18f0ce6aab/color_cycle.py)
- [Download Jupyter notebook: color_cycle.ipynb](https://matplotlib.org/_downloads/e2174f7bdc06ad628a756f14967811ee/color_cycle.ipynb)

View File

@@ -0,0 +1,900 @@
---
sidebarDepth: 3
sidebar: auto
---
# Constrained Layout Guide
How to use constrained-layout to fit plots within your figure cleanly.
*constrained_layout* automatically adjusts subplots and decorations like
legends and colorbars so that they fit in the figure window while still
preserving, as best they can, the logical layout requested by the user.
*constrained_layout* is similar to
[tight_layout](tight_layout_guide.html),
but uses a constraint solver to determine the size of axes that allows
them to fit.
*constrained_layout* needs to be activated before any axes are added to
a figure. Two ways of doing so are
- using the respective argument to [``subplots()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplots.html#matplotlib.pyplot.subplots) or
[``figure()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure), e.g.:
``` python
plt.subplots(constrained_layout=True)
```
- activate it via [rcParams](https://matplotlib.org/introductory/customizing.html#matplotlib-rcparams), like:
``` python
plt.rcParams['figure.constrained_layout.use'] = True
```
Those are described in detail throughout the following sections.
::: danger Warning
Currently Constrained Layout is **experimental**. The
behaviour and API are subject to change, or the whole functionality
may be removed without a deprecation period. If you *require* your
plots to be absolutely reproducible, get the Axes positions after
running Constrained Layout and use ``ax.set_position()`` in your code
with ``constrained_layout=False``.
:::
## Simple Example
In Matplotlib, the location of axes (including subplots) are specified in
normalized figure coordinates. It can happen that your axis labels or
titles (or sometimes even ticklabels) go outside the figure area, and are thus
clipped.
``` python
# sphinx_gallery_thumbnail_number = 18
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.gridspec as gridspec
import numpy as np
plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.
def example_plot(ax, fontsize=12, nodec=False):
ax.plot([1, 2])
ax.locator_params(nbins=3)
if not nodec:
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)
else:
ax.set_xticklabels('')
ax.set_yticklabels('')
fig, ax = plt.subplots(constrained_layout=False)
example_plot(ax, fontsize=24)
```
![sphx_glr_constrainedlayout_guide_001](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_001.png)
To prevent this, the location of axes needs to be adjusted. For
subplots, this can be done by adjusting the subplot params
([Move the edge of an axes to make room for tick labels](https://matplotlib.orgfaq/howto_faq.html#howto-subplots-adjust)). However, specifying your figure with the
``constrained_layout=True`` kwarg will do the adjusting automatically.
``` python
fig, ax = plt.subplots(constrained_layout=True)
example_plot(ax, fontsize=24)
```
![sphx_glr_constrainedlayout_guide_002](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_002.png)
When you have multiple subplots, often you see labels of different
axes overlapping each other.
``` python
fig, axs = plt.subplots(2, 2, constrained_layout=False)
for ax in axs.flat:
example_plot(ax)
```
![sphx_glr_constrainedlayout_guide_003](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_003.png)
Specifying ``constrained_layout=True`` in the call to ``plt.subplots``
causes the layout to be properly constrained.
``` python
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
example_plot(ax)
```
![sphx_glr_constrainedlayout_guide_004](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_004.png)
## Colorbars
If you create a colorbar with the [``colorbar()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.colorbar.html#matplotlib.pyplot.colorbar)
command you need to make room for it. ``constrained_layout`` does this
automatically. Note that if you specify ``use_gridspec=True`` it will be
ignored because this option is made for improving the layout via
``tight_layout``.
::: tip Note
For the [``pcolormesh``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.pcolormesh.html#matplotlib.axes.Axes.pcolormesh) kwargs (``pc_kwargs``) we use a
dictionary. Below we will assign one colorbar to a number of axes each
containing a [``ScalarMappable``](https://matplotlib.orgapi/cm_api.html#matplotlib.cm.ScalarMappable); specifying the norm and colormap
ensures the colorbar is accurate for all the axes.
:::
``` python
arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
# see note above: this makes all pcolormesh calls consistent:
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}
fig, ax = plt.subplots(figsize=(4, 4), constrained_layout=True)
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=ax, shrink=0.6)
```
![sphx_glr_constrainedlayout_guide_005](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_005.png)
If you specify a list of axes (or other iterable container) to the
``ax`` argument of ``colorbar``, constrained_layout will take space from
the specified axes.
``` python
fig, axs = plt.subplots(2, 2, figsize=(4, 4), constrained_layout=True)
for ax in axs.flat:
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)
```
![sphx_glr_constrainedlayout_guide_006](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_006.png)
If you specify a list of axes from inside a grid of axes, the colorbar
will steal space appropriately, and leave a gap, but all subplots will
still be the same size.
``` python
fig, axs = plt.subplots(3, 3, figsize=(4, 4), constrained_layout=True)
for ax in axs.flat:
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[1:, ][:, 1], shrink=0.8)
fig.colorbar(im, ax=axs[:, -1], shrink=0.6)
```
![sphx_glr_constrainedlayout_guide_007](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_007.png)
Note that there is a bit of a subtlety when specifying a single axes
as the parent. In the following, it might be desirable and expected
for the colorbars to line up, but they don't because the colorbar paired
with the bottom axes is tied to the subplotspec of the axes, and hence
shrinks when the gridspec-level colorbar is added.
``` python
fig, axs = plt.subplots(3, 1, figsize=(4, 4), constrained_layout=True)
for ax in axs[:2]:
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[:2], shrink=0.6)
im = axs[2].pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[2], shrink=0.6)
```
![sphx_glr_constrainedlayout_guide_008](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_008.png)
The API to make a single-axes behave like a list of axes is to specify
it as a list (or other iterable container), as below:
``` python
fig, axs = plt.subplots(3, 1, figsize=(4, 4), constrained_layout=True)
for ax in axs[:2]:
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[:2], shrink=0.6)
im = axs[2].pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=[axs[2]], shrink=0.6)
```
![sphx_glr_constrainedlayout_guide_009](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_009.png)
## Suptitle
``constrained_layout`` can also make room for [``suptitle``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.suptitle).
``` python
fig, axs = plt.subplots(2, 2, figsize=(4, 4), constrained_layout=True)
for ax in axs.flat:
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)
fig.suptitle('Big Suptitle')
```
![sphx_glr_constrainedlayout_guide_010](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_010.png)
## Legends
Legends can be placed outside of their parent axis.
Constrained-layout is designed to handle this for [``Axes.legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.legend.html#matplotlib.axes.Axes.legend).
However, constrained-layout does *not* handle legends being created via
[``Figure.legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.legend) (yet).
``` python
fig, ax = plt.subplots(constrained_layout=True)
ax.plot(np.arange(10), label='This is a plot')
ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
```
![sphx_glr_constrainedlayout_guide_011](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_011.png)
However, this will steal space from a subplot layout:
``` python
fig, axs = plt.subplots(1, 2, figsize=(4, 2), constrained_layout=True)
axs[0].plot(np.arange(10))
axs[1].plot(np.arange(10), label='This is a plot')
axs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
```
![sphx_glr_constrainedlayout_guide_012](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_012.png)
In order for a legend or other artist to *not* steal space
from the subplot layout, we can ``leg.set_in_layout(False)``.
Of course this can mean the legend ends up
cropped, but can be useful if the plot is subsequently called
with ``fig.savefig('outname.png', bbox_inches='tight')``. Note,
however, that the legend's ``get_in_layout`` status will have to be
toggled again to make the saved file work, and we must manually
trigger a draw if we want constrained_layout to adjust the size
of the axes before printing.
``` python
fig, axs = plt.subplots(1, 2, figsize=(4, 2), constrained_layout=True)
axs[0].plot(np.arange(10))
axs[1].plot(np.arange(10), label='This is a plot')
leg = axs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
leg.set_in_layout(False)
# trigger a draw so that constrained_layout is executed once
# before we turn it off when printing....
fig.canvas.draw()
# we want the legend included in the bbox_inches='tight' calcs.
leg.set_in_layout(True)
# we don't want the layout to change at this point.
fig.set_constrained_layout(False)
fig.savefig('CL01.png', bbox_inches='tight', dpi=100)
```
![sphx_glr_constrainedlayout_guide_013](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_013.png)
The saved file looks like:
![CL01](https://matplotlib.org/_images/CL01.png)
A better way to get around this awkwardness is to simply
use the legend method provided by [``Figure.legend``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.legend):
``` python
fig, axs = plt.subplots(1, 2, figsize=(4, 2), constrained_layout=True)
axs[0].plot(np.arange(10))
lines = axs[1].plot(np.arange(10), label='This is a plot')
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left',
bbox_to_anchor=(0.8, 0.5), bbox_transform=axs[1].transAxes)
fig.savefig('CL02.png', bbox_inches='tight', dpi=100)
```
![sphx_glr_constrainedlayout_guide_014](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_014.png)
The saved file looks like:
![CL02](https://matplotlib.org/_images/CL02.png)
## Padding and Spacing
For constrained_layout, we have implemented a padding around the edge of
each axes. This padding sets the distance from the edge of the plot,
and the minimum distance between adjacent plots. It is specified in
inches by the keyword arguments ``w_pad`` and ``h_pad`` to the function
[``set_constrained_layout_pads``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.set_constrained_layout_pads):
``` python
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
example_plot(ax, nodec=True)
ax.set_xticklabels('')
ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=4./72., h_pad=4./72.,
hspace=0., wspace=0.)
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
example_plot(ax, nodec=True)
ax.set_xticklabels('')
ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
hspace=0., wspace=0.)
```
- ![sphx_glr_constrainedlayout_guide_015](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_015.png)
- ![sphx_glr_constrainedlayout_guide_016](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_016.png)
Spacing between subplots is set by ``wspace`` and ``hspace``. There are
specified as a fraction of the size of the subplot group as a whole.
If the size of the figure is changed, then these spaces change in
proportion. Note in the blow how the space at the edges doesn't change from
the above, but the space between subplots does.
``` python
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
example_plot(ax, nodec=True)
ax.set_xticklabels('')
ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
hspace=0.2, wspace=0.2)
```
![sphx_glr_constrainedlayout_guide_017](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_017.png)
### Spacing with colorbars
Colorbars will be placed ``wspace`` and ``hsapce`` apart from other
subplots. The padding between the colorbar and the axis it is
attached to will never be less than ``w_pad`` (for a vertical colorbar)
or ``h_pad`` (for a horizontal colorbar). Note the use of the ``pad`` kwarg
here in the ``colorbar`` call. It defaults to 0.02 of the size
of the axis it is attached to.
``` python
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
pc = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(pc, ax=ax, shrink=0.6, pad=0)
ax.set_xticklabels('')
ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
hspace=0.2, wspace=0.2)
```
![sphx_glr_constrainedlayout_guide_018](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_018.png)
In the above example, the colorbar will not ever be closer than 2 pts to
the plot, but if we want it a bit further away, we can specify its value
for ``pad`` to be non-zero.
``` python
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
pc = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=ax, shrink=0.6, pad=0.05)
ax.set_xticklabels('')
ax.set_yticklabels('')
fig.set_constrained_layout_pads(w_pad=2./72., h_pad=2./72.,
hspace=0.2, wspace=0.2)
```
![sphx_glr_constrainedlayout_guide_019](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_019.png)
## rcParams
There are five [rcParams](https://matplotlib.org/introductory/customizing.html#matplotlib-rcparams) that can be set,
either in a script or in the ``matplotlibrc`` file.
They all have the prefix ``figure.constrained_layout``:
- ``use``: Whether to use constrained_layout. Default is False
- ``w_pad``, ``h_pad``: Padding around axes objects.
Float representing inches. Default is 3./72. inches (3 pts)
- ``wspace``, ``hspace``: Space between subplot groups.
Float representing a fraction of the subplot widths being separated.
Default is 0.02.
``` python
plt.rcParams['figure.constrained_layout.use'] = True
fig, axs = plt.subplots(2, 2, figsize=(3, 3))
for ax in axs.flat:
example_plot(ax)
```
![sphx_glr_constrainedlayout_guide_020](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_020.png)
## Use with GridSpec
constrained_layout is meant to be used
with [``subplots()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.subplots) or
[``GridSpec()``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec) and
[``add_subplot()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_subplot).
Note that in what follows ``constrained_layout=True``
``` python
fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1, figure=fig)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
```
![sphx_glr_constrainedlayout_guide_021](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_021.png)
More complicated gridspec layouts are possible. Note here we use the
convenience functions ``add_gridspec`` and ``subgridspec``.
``` python
fig = plt.figure()
gs0 = fig.add_gridspec(1, 2)
gs1 = gs0[0].subgridspec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs2 = gs0[1].subgridspec(3, 1)
for ss in gs2:
ax = fig.add_subplot(ss)
example_plot(ax)
ax.set_title("")
ax.set_xlabel("")
ax.set_xlabel("x-label", fontsize=12)
```
![sphx_glr_constrainedlayout_guide_022](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_022.png)
Note that in the above the left and columns don't have the same vertical
extent. If we want the top and bottom of the two grids to line up then
they need to be in the same gridspec:
``` python
fig = plt.figure()
gs0 = fig.add_gridspec(6, 2)
ax1 = fig.add_subplot(gs0[:3, 0])
ax2 = fig.add_subplot(gs0[3:, 0])
example_plot(ax1)
example_plot(ax2)
ax = fig.add_subplot(gs0[0:2, 1])
example_plot(ax)
ax = fig.add_subplot(gs0[2:4, 1])
example_plot(ax)
ax = fig.add_subplot(gs0[4:, 1])
example_plot(ax)
```
![sphx_glr_constrainedlayout_guide_023](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_023.png)
This example uses two gridspecs to have the colorbar only pertain to
one set of pcolors. Note how the left column is wider than the
two right-hand columns because of this. Of course, if you wanted the
subplots to be the same size you only needed one gridspec.
``` python
def docomplicated(suptitle=None):
fig = plt.figure()
gs0 = fig.add_gridspec(1, 2, figure=fig, width_ratios=[1., 2.])
gsl = gs0[0].subgridspec(2, 1)
gsr = gs0[1].subgridspec(2, 2)
for gs in gsl:
ax = fig.add_subplot(gs)
example_plot(ax)
axs = []
for gs in gsr:
ax = fig.add_subplot(gs)
pcm = ax.pcolormesh(arr, **pc_kwargs)
ax.set_xlabel('x-label')
ax.set_ylabel('y-label')
ax.set_title('title')
axs += [ax]
fig.colorbar(pcm, ax=axs)
if suptitle is not None:
fig.suptitle(suptitle)
docomplicated()
```
![sphx_glr_constrainedlayout_guide_024](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_024.png)
## Manually setting axes positions
There can be good reasons to manually set an axes position. A manual call
to [``set_position``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.set_position.html#matplotlib.axes.Axes.set_position) will set the axes so constrained_layout has
no effect on it anymore. (Note that constrained_layout still leaves the
space for the axes that is moved).
``` python
fig, axs = plt.subplots(1, 2)
example_plot(axs[0], fontsize=12)
axs[1].set_position([0.2, 0.2, 0.4, 0.4])
```
![sphx_glr_constrainedlayout_guide_025](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_025.png)
If you want an inset axes in data-space, you need to manually execute the
layout using ``fig.execute_constrained_layout()`` call. The inset figure
will then be properly positioned. However, it will not be properly
positioned if the size of the figure is subsequently changed. Similarly,
if the figure is printed to another backend, there may be slight changes
of location due to small differences in how the backends render fonts.
``` python
from matplotlib.transforms import Bbox
fig, axs = plt.subplots(1, 2)
example_plot(axs[0], fontsize=12)
fig.execute_constrained_layout()
# put into data-space:
bb_data_ax2 = Bbox.from_bounds(0.5, 1., 0.2, 0.4)
disp_coords = axs[0].transData.transform(bb_data_ax2)
fig_coords_ax2 = fig.transFigure.inverted().transform(disp_coords)
bb_ax2 = Bbox(fig_coords_ax2)
ax2 = fig.add_axes(bb_ax2)
```
![sphx_glr_constrainedlayout_guide_026](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_026.png)
## Manually turning off ``constrained_layout``
``constrained_layout`` usually adjusts the axes positions on each draw
of the figure. If you want to get the spacing provided by
``constrained_layout`` but not have it update, then do the initial
draw and then call ``fig.set_constrained_layout(False)``.
This is potentially useful for animations where the tick labels may
change length.
Note that ``constrained_layout`` is turned off for ``ZOOM`` and ``PAN``
GUI events for the backends that use the toolbar. This prevents the
axes from changing position during zooming and panning.
## Limitations
### Incompatible functions
``constrained_layout`` will not work on subplots
created via the ``subplot`` command. The reason is that each of these
commands creates a separate ``GridSpec`` instance and ``constrained_layout``
uses (nested) gridspecs to carry out the layout. So the following fails
to yield a nice layout:
``` python
fig = plt.figure()
ax1 = plt.subplot(221)
ax2 = plt.subplot(223)
ax3 = plt.subplot(122)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
```
![sphx_glr_constrainedlayout_guide_027](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_027.png)
Of course that layout is possible using a gridspec:
``` python
fig = plt.figure()
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0])
ax3 = fig.add_subplot(gs[:, 1])
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
```
![sphx_glr_constrainedlayout_guide_028](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_028.png)
Similarly,
[``subplot2grid()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplot2grid.html#matplotlib.pyplot.subplot2grid) doesn't work for the same reason:
each call creates a different parent gridspec.
``` python
fig = plt.figure()
ax1 = plt.subplot2grid((3, 3), (0, 0))
ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
```
![sphx_glr_constrainedlayout_guide_029](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_029.png)
The way to make this plot compatible with ``constrained_layout`` is again
to use ``gridspec`` directly
``` python
fig = plt.figure()
gs = fig.add_gridspec(3, 3)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:])
ax3 = fig.add_subplot(gs[1:, 0:2])
ax4 = fig.add_subplot(gs[1:, -1])
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
```
![sphx_glr_constrainedlayout_guide_030](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_030.png)
### Other Caveats
- ``constrained_layout`` only considers ticklabels, axis labels, titles, and
legends. Thus, other artists may be clipped and also may overlap.
- It assumes that the extra space needed for ticklabels, axis labels,
and titles is independent of original location of axes. This is
often true, but there are rare cases where it is not.
- There are small differences in how the backends handle rendering fonts,
so the results will not be pixel-identical.
## Debugging
Constrained-layout can fail in somewhat unexpected ways. Because it uses
a constraint solver the solver can find solutions that are mathematically
correct, but that aren't at all what the user wants. The usual failure
mode is for all sizes to collapse to their smallest allowable value. If
this happens, it is for one of two reasons:
1. There was not enough room for the elements you were requesting to draw.
1. There is a bug - in which case open an issue at
[https://github.com/matplotlib/matplotlib/issues](https://github.com/matplotlib/matplotlib/issues).
If there is a bug, please report with a self-contained example that does
not require outside data or dependencies (other than numpy).
## Notes on the algorithm
The algorithm for the constraint is relatively straightforward, but
has some complexity due to the complex ways we can layout a figure.
### Figure layout
Figures are laid out in a hierarchy:
1. Figure: ``fig = plt.figure()``
Each item has a layoutbox associated with it. The nesting of gridspecs
created with [``GridSpecFromSubplotSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpecFromSubplotSpec.html#matplotlib.gridspec.GridSpecFromSubplotSpec) can be arbitrarily deep.
Each [``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) has *two* layoutboxes. The first one,
``ax._layoutbox`` represents the outside of the Axes and all its
decorations (i.e. ticklabels,axis labels, etc.).
The second layoutbox corresponds to the Axes' ``ax.position``, which sets
where in the figure the spines are placed.
Why so many stacked containers? Ideally, all that would be needed are the
Axes layout boxes. For the Gridspec case, a container is
needed if the Gridspec is nested via [``GridSpecFromSubplotSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpecFromSubplotSpec.html#matplotlib.gridspec.GridSpecFromSubplotSpec). At the
top level, it is desirable for symmetry, but it also makes room for
[``suptitle``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.suptitle).
For the Subplotspec/Axes case, Axes often have colorbars or other
annotations that need to be packaged inside the Subplotspec, hence the
need for the outer layer.
### Simple case: one Axes
For a single Axes the layout is straight forward. The Figure and
outer Gridspec layoutboxes coincide. The Subplotspec and Axes
boxes also coincide because the Axes has no colorbar. Note
the difference between the red ``pos`` box and the green ``ax`` box
is set by the size of the decorations around the Axes.
In the code, this is accomplished by the entries in
``do_constrained_layout()`` like:
``` python
ax._poslayoutbox.edit_left_margin_min(-bbox.x0 + pos.x0 + w_padt)
```
``` python
from matplotlib._layoutbox import plot_children
fig, ax = plt.subplots(constrained_layout=True)
example_plot(ax, fontsize=24)
plot_children(fig, fig._layoutbox, printit=False)
```
![sphx_glr_constrainedlayout_guide_031](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_031.png)
### Simple case: two Axes
For this case, the Axes layoutboxes and the Subplotspec boxes still
co-incide. However, because the decorations in the right-hand plot are so
much smaller than the left-hand, so the right-hand layoutboxes are smaller.
The Subplotspec boxes are laid out in the code in the subroutine
``arange_subplotspecs()``, which simply checks the subplotspecs in the code
against one another and stacks them appropriately.
The two ``pos`` axes are lined up. Because they have the same
minimum row, they are lined up at the top. Because
they have the same maximum row they are lined up at the bottom. In the
code this is accomplished via the calls to ``layoutbox.align``. If
there was more than one row, then the same horizontal alignment would
occur between the rows.
The two ``pos`` axes are given the same width because the subplotspecs
occupy the same number of columns. This is accomplished in the code where
``dcols0`` is compared to ``dcolsC``. If they are equal, then their widths
are constrained to be equal.
While it is a bit subtle in this case, note that the division between the
Subplotspecs is *not* centered, but has been moved to the right to make
space for the larger labels on the left-hand plot.
``` python
fig, ax = plt.subplots(1, 2, constrained_layout=True)
example_plot(ax[0], fontsize=32)
example_plot(ax[1], fontsize=8)
plot_children(fig, fig._layoutbox, printit=False)
```
![sphx_glr_constrainedlayout_guide_032](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_032.png)
### Two Axes and colorbar
Adding a colorbar makes it clear why the Subplotspec layoutboxes must
be different from the axes layoutboxes. Here we see the left-hand
subplotspec has more room to accommodate the [``colorbar``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.colorbar), and
that there are two green ``ax`` boxes inside the ``ss`` box.
Note that the width of the ``pos`` boxes is still the same because of the
constraint on their widths because their subplotspecs occupy the same
number of columns (one in this example).
The colorbar layout logic is contained in [``make_axes``](https://matplotlib.orgapi/colorbar_api.html#matplotlib.colorbar.make_axes)
which calls ``_constrained_layout.layoutcolorbarsingle()``
for cbars attached to a single axes, and
``_constrained_layout.layoutcolorbargridspec()`` if the colorbar is
associated with a gridspec.
``` python
fig, ax = plt.subplots(1, 2, constrained_layout=True)
im = ax[0].pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=ax[0], shrink=0.6)
im = ax[1].pcolormesh(arr, **pc_kwargs)
plot_children(fig, fig._layoutbox, printit=False)
```
![sphx_glr_constrainedlayout_guide_033](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_033.png)
### Colorbar associated with a Gridspec
This example shows the Subplotspec layoutboxes being made smaller by
a colorbar layoutbox. The size of the colorbar layoutbox is
set to be ``shrink`` smaller than the vertical extent of the ``pos``
layoutboxes in the gridspec, and it is made to be centered between
those two points.
``` python
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)
plot_children(fig, fig._layoutbox, printit=False)
```
![sphx_glr_constrainedlayout_guide_034](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_034.png)
### Uneven sized Axes
There are two ways to make axes have an uneven size in a
Gridspec layout, either by specifying them to cross Gridspecs rows
or columns, or by specifying width and height ratios.
The first method is used here. The constraint that makes the heights
be correct is in the code where ``drowsC < drows0`` which in
this case would be 1 is less than 2. So we constrain the
height of the 1-row Axes to be less than half the height of the
2-row Axes.
::: tip Note
This algorithm can be wrong if the decorations attached to the smaller
axes are very large, so there is an unaccounted-for edge case.
:::
``` python
fig = plt.figure(constrained_layout=True)
gs = gridspec.GridSpec(2, 2, figure=fig)
ax = fig.add_subplot(gs[:, 0])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[0, 1])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[1, 1])
im = ax.pcolormesh(arr, **pc_kwargs)
plot_children(fig, fig._layoutbox, printit=False)
```
![sphx_glr_constrainedlayout_guide_035](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_035.png)
Height and width ratios are accommodated with the same part of
the code with the smaller axes always constrained to be less in size
than the larger.
``` python
fig = plt.figure(constrained_layout=True)
gs = gridspec.GridSpec(3, 2, figure=fig,
height_ratios=[1., 0.5, 1.5],
width_ratios=[1.2, 0.8])
ax = fig.add_subplot(gs[:2, 0])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[2, 0])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[0, 1])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[1:, 1])
im = ax.pcolormesh(arr, **pc_kwargs)
plot_children(fig, fig._layoutbox, printit=False)
```
![sphx_glr_constrainedlayout_guide_036](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_036.png)
### Empty gridspec slots
The final piece of the code that has not been explained is what happens if
there is an empty gridspec opening. In that case a fake invisible axes is
added and we proceed as before. The empty gridspec has no decorations, but
the axes position in made the same size as the occupied Axes positions.
This is done at the start of
``_constrained_layout.do_constrained_layout()`` (``hassubplotspec``).
``` python
fig = plt.figure(constrained_layout=True)
gs = gridspec.GridSpec(1, 3, figure=fig)
ax = fig.add_subplot(gs[0])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[-1])
im = ax.pcolormesh(arr, **pc_kwargs)
plot_children(fig, fig._layoutbox, printit=False)
plt.show()
```
![sphx_glr_constrainedlayout_guide_037](https://matplotlib.org/_images/sphx_glr_constrainedlayout_guide_037.png)
### Other notes
The layout is called only once. This is OK if the original layout was
pretty close (which it should be in most cases). However, if the layout
changes a lot from the default layout then the decorators can change size.
In particular the x and ytick labels can change. If this happens, then
we should probably call the whole routine twice.
**Total running time of the script:** ( 0 minutes 15.551 seconds)
## Download
- [Download Python source code: constrainedlayout_guide.py](https://matplotlib.org/_downloads/20cdf5d6a41b563e2ad7f13d2f8eb742/constrainedlayout_guide.py)
- [Download Jupyter notebook: constrainedlayout_guide.ipynb](https://matplotlib.org/_downloads/22d57b5ff690950502e071d423750e4a/constrainedlayout_guide.ipynb)

View File

@@ -0,0 +1,305 @@
---
sidebarDepth: 3
sidebar: auto
---
# Customizing Figure Layouts Using GridSpec and Other Functions
How to create grid-shaped combinations of axes.
[``subplots()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplots.html#matplotlib.pyplot.subplots)
[``GridSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec)
[``SubplotSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.SubplotSpec.html#matplotlib.gridspec.SubplotSpec)
[``subplot2grid()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplot2grid.html#matplotlib.pyplot.subplot2grid)
``` python
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
```
## Basic Quickstart Guide
These first two examples show how to create a basic 2-by-2 grid using
both [``subplots()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplots.html#matplotlib.pyplot.subplots) and [``gridspec``](https://matplotlib.orgapi/gridspec_api.html#module-matplotlib.gridspec).
Using [``subplots()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplots.html#matplotlib.pyplot.subplots) is quite simple.
It returns a [``Figure``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure) instance and an array of
[``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) objects.
``` python
fig1, f1_axes = plt.subplots(ncols=2, nrows=2, constrained_layout=True)
```
![sphx_glr_gridspec_001](https://matplotlib.org/_images/sphx_glr_gridspec_001.png)
For a simple use case such as this, [``gridspec``](https://matplotlib.orgapi/gridspec_api.html#module-matplotlib.gridspec) is
perhaps overly verbose.
You have to create the figure and [``GridSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec)
instance separately, then pass elements of gridspec instance to the
[``add_subplot()``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_subplot) method to create the axes
objects.
The elements of the gridspec are accessed in generally the same manner as
numpy arrays.
``` python
fig2 = plt.figure(constrained_layout=True)
spec2 = gridspec.GridSpec(ncols=2, nrows=2, figure=fig2)
f2_ax1 = fig2.add_subplot(spec2[0, 0])
f2_ax2 = fig2.add_subplot(spec2[0, 1])
f2_ax3 = fig2.add_subplot(spec2[1, 0])
f2_ax4 = fig2.add_subplot(spec2[1, 1])
```
![sphx_glr_gridspec_002](https://matplotlib.org/_images/sphx_glr_gridspec_002.png)
The power of gridspec comes in being able to create subplots that span
rows and columns. Note the
[Numpy slice](https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html)
syntax for selecting the part of the gridspec each subplot will occupy.
Note that we have also used the convenience method [``Figure.add_gridspec``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_gridspec)
instead of [``gridspec.GridSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec), potentially saving the user an import,
and keeping the namespace cleaner.
``` python
fig3 = plt.figure(constrained_layout=True)
gs = fig3.add_gridspec(3, 3)
f3_ax1 = fig3.add_subplot(gs[0, :])
f3_ax1.set_title('gs[0, :]')
f3_ax2 = fig3.add_subplot(gs[1, :-1])
f3_ax2.set_title('gs[1, :-1]')
f3_ax3 = fig3.add_subplot(gs[1:, -1])
f3_ax3.set_title('gs[1:, -1]')
f3_ax4 = fig3.add_subplot(gs[-1, 0])
f3_ax4.set_title('gs[-1, 0]')
f3_ax5 = fig3.add_subplot(gs[-1, -2])
f3_ax5.set_title('gs[-1, -2]')
```
![sphx_glr_gridspec_003](https://matplotlib.org/_images/sphx_glr_gridspec_003.png)
[``gridspec``](https://matplotlib.orgapi/gridspec_api.html#module-matplotlib.gridspec) is also indispensable for creating subplots
of different widths via a couple of methods.
The method shown here is similar to the one above and initializes a
uniform grid specification,
and then uses numpy indexing and slices to allocate multiple
"cells" for a given subplot.
``` python
fig4 = plt.figure(constrained_layout=True)
spec4 = fig4.add_gridspec(ncols=2, nrows=2)
anno_opts = dict(xy=(0.5, 0.5), xycoords='axes fraction',
va='center', ha='center')
f4_ax1 = fig4.add_subplot(spec4[0, 0])
f4_ax1.annotate('GridSpec[0, 0]', **anno_opts)
fig4.add_subplot(spec4[0, 1]).annotate('GridSpec[0, 1:]', **anno_opts)
fig4.add_subplot(spec4[1, 0]).annotate('GridSpec[1:, 0]', **anno_opts)
fig4.add_subplot(spec4[1, 1]).annotate('GridSpec[1:, 1:]', **anno_opts)
```
![sphx_glr_gridspec_004](https://matplotlib.org/_images/sphx_glr_gridspec_004.png)
Another option is to use the ``width_ratios`` and ``height_ratios``
parameters. These keyword arguments are lists of numbers.
Note that absolute values are meaningless, only their relative ratios
matter. That means that ``width_ratios=[2, 4, 8]`` is equivalent to
``width_ratios=[1, 2, 4]`` within equally wide figures.
For the sake of demonstration, we'll blindly create the axes within
``for`` loops since we won't need them later.
``` python
fig5 = plt.figure(constrained_layout=True)
widths = [2, 3, 1.5]
heights = [1, 3, 2]
spec5 = fig5.add_gridspec(ncols=3, nrows=3, width_ratios=widths,
height_ratios=heights)
for row in range(3):
for col in range(3):
ax = fig5.add_subplot(spec5[row, col])
label = 'Width: {}\nHeight: {}'.format(widths[col], heights[row])
ax.annotate(label, (0.1, 0.5), xycoords='axes fraction', va='center')
```
![sphx_glr_gridspec_005](https://matplotlib.org/_images/sphx_glr_gridspec_005.png)
Learning to use ``width_ratios`` and ``height_ratios`` is particularly
useful since the top-level function [``subplots()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplots.html#matplotlib.pyplot.subplots)
accepts them within the ``gridspec_kw`` parameter.
For that matter, any parameter accepted by
[``GridSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec) can be passed to
[``subplots()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplots.html#matplotlib.pyplot.subplots) via the ``gridspec_kw`` parameter.
This example recreates the previous figure without directly using a
gridspec instance.
``` python
gs_kw = dict(width_ratios=widths, height_ratios=heights)
fig6, f6_axes = plt.subplots(ncols=3, nrows=3, constrained_layout=True,
gridspec_kw=gs_kw)
for r, row in enumerate(f6_axes):
for c, ax in enumerate(row):
label = 'Width: {}\nHeight: {}'.format(widths[c], heights[r])
ax.annotate(label, (0.1, 0.5), xycoords='axes fraction', va='center')
```
![sphx_glr_gridspec_006](https://matplotlib.org/_images/sphx_glr_gridspec_006.png)
The ``subplots`` and ``gridspec`` methods can be combined since it is
sometimes more convenient to make most of the subplots using ``subplots``
and then remove some and combine them. Here we create a layout with
the bottom two axes in the last column combined.
``` python
fig7, f7_axs = plt.subplots(ncols=3, nrows=3)
gs = f7_axs[1, 2].get_gridspec()
# remove the underlying axes
for ax in f7_axs[1:, -1]:
ax.remove()
axbig = fig7.add_subplot(gs[1:, -1])
axbig.annotate('Big Axes \nGridSpec[1:, -1]', (0.1, 0.5),
xycoords='axes fraction', va='center')
fig7.tight_layout()
```
![sphx_glr_gridspec_007](https://matplotlib.org/_images/sphx_glr_gridspec_007.png)
## Fine Adjustments to a Gridspec Layout
When a GridSpec is explicitly used, you can adjust the layout
parameters of subplots that are created from the GridSpec. Note this
option is not compatible with ``constrained_layout`` or
[``Figure.tight_layout``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.tight_layout) which both adjust subplot sizes to fill the
figure.
``` python
fig8 = plt.figure(constrained_layout=False)
gs1 = fig8.add_gridspec(nrows=3, ncols=3, left=0.05, right=0.48, wspace=0.05)
f8_ax1 = fig8.add_subplot(gs1[:-1, :])
f8_ax2 = fig8.add_subplot(gs1[-1, :-1])
f8_ax3 = fig8.add_subplot(gs1[-1, -1])
```
![sphx_glr_gridspec_008](https://matplotlib.org/_images/sphx_glr_gridspec_008.png)
This is similar to [``subplots_adjust()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplots_adjust.html#matplotlib.pyplot.subplots_adjust), but it only
affects the subplots that are created from the given GridSpec.
For example, compare the left and right sides of this figure:
``` python
fig9 = plt.figure(constrained_layout=False)
gs1 = fig9.add_gridspec(nrows=3, ncols=3, left=0.05, right=0.48,
wspace=0.05)
f9_ax1 = fig9.add_subplot(gs1[:-1, :])
f9_ax2 = fig9.add_subplot(gs1[-1, :-1])
f9_ax3 = fig9.add_subplot(gs1[-1, -1])
gs2 = fig9.add_gridspec(nrows=3, ncols=3, left=0.55, right=0.98,
hspace=0.05)
f9_ax4 = fig9.add_subplot(gs2[:, :-1])
f9_ax5 = fig9.add_subplot(gs2[:-1, -1])
f9_ax6 = fig9.add_subplot(gs2[-1, -1])
```
![sphx_glr_gridspec_009](https://matplotlib.org/_images/sphx_glr_gridspec_009.png)
## GridSpec using SubplotSpec
You can create GridSpec from the [``SubplotSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.SubplotSpec.html#matplotlib.gridspec.SubplotSpec),
in which case its layout parameters are set to that of the location of
the given SubplotSpec.
Note this is also available from the more verbose
[``gridspec.GridSpecFromSubplotSpec``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpecFromSubplotSpec.html#matplotlib.gridspec.GridSpecFromSubplotSpec).
``` python
fig10 = plt.figure(constrained_layout=True)
gs0 = fig10.add_gridspec(1, 2)
gs00 = gs0[0].subgridspec(2, 3)
gs01 = gs0[1].subgridspec(3, 2)
for a in range(2):
for b in range(3):
fig10.add_subplot(gs00[a, b])
fig10.add_subplot(gs01[b, a])
```
![sphx_glr_gridspec_010](https://matplotlib.org/_images/sphx_glr_gridspec_010.png)
## A Complex Nested GridSpec using SubplotSpec
Here's a more sophisticated example of nested GridSpec where we put
a box around each cell of the outer 4x4 grid, by hiding appropriate
spines in each of the inner 3x3 grids.
``` python
import numpy as np
from itertools import product
def squiggle_xy(a, b, c, d, i=np.arange(0.0, 2*np.pi, 0.05)):
return np.sin(i*a)*np.cos(i*b), np.sin(i*c)*np.cos(i*d)
fig11 = plt.figure(figsize=(8, 8), constrained_layout=False)
# gridspec inside gridspec
outer_grid = fig11.add_gridspec(4, 4, wspace=0.0, hspace=0.0)
for i in range(16):
inner_grid = outer_grid[i].subgridspec(3, 3, wspace=0.0, hspace=0.0)
a, b = int(i/4)+1, i % 4+1
for j, (c, d) in enumerate(product(range(1, 4), repeat=2)):
ax = fig11.add_subplot(inner_grid[j])
ax.plot(*squiggle_xy(a, b, c, d))
ax.set_xticks([])
ax.set_yticks([])
fig11.add_subplot(ax)
all_axes = fig11.get_axes()
# show only the outside spines
for ax in all_axes:
for sp in ax.spines.values():
sp.set_visible(False)
if ax.is_first_row():
ax.spines['top'].set_visible(True)
if ax.is_last_row():
ax.spines['bottom'].set_visible(True)
if ax.is_first_col():
ax.spines['left'].set_visible(True)
if ax.is_last_col():
ax.spines['right'].set_visible(True)
plt.show()
```
![sphx_glr_gridspec_011](https://matplotlib.org/_images/sphx_glr_gridspec_011.png)
### References
The usage of the following functions and methods is shown in this example:
``` python
matplotlib.pyplot.subplots
matplotlib.figure.Figure.add_gridspec
matplotlib.figure.Figure.add_subplot
matplotlib.gridspec.GridSpec
matplotlib.gridspec.SubplotSpec.subgridspec
matplotlib.gridspec.GridSpecFromSubplotSpec
```
**Total running time of the script:** ( 0 minutes 8.732 seconds)
## Download
- [Download Python source code: gridspec.py](https://matplotlib.org/_downloads/54501e30d0a29665618afe715673cb41/gridspec.py)
- [Download Jupyter notebook: gridspec.ipynb](https://matplotlib.org/_downloads/0eaf234b06f4f7a6a52fa9ca11b63755/gridspec.ipynb)

View File

@@ -0,0 +1,295 @@
---
sidebarDepth: 3
sidebar: auto
---
# origin and extent in ``imshow``
[``imshow()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.imshow.html#matplotlib.axes.Axes.imshow) allows you to render an image (either a 2D array
which will be color-mapped (based on *norm* and *cmap*) or and 3D RGB(A)
array which will be used as-is) to a rectangular region in dataspace.
The orientation of the image in the final rendering is controlled by
the *origin* and *extent* kwargs (and attributes on the resulting
[``AxesImage``](https://matplotlib.orgapi/image_api.html#matplotlib.image.AxesImage) instance) and the data limits of the axes.
The *extent* kwarg controls the bounding box in data coordinates that
the image will fill specified as ``(left, right, bottom, top)`` in
**data coordinates**, the *origin* kwarg controls how the image fills
that bounding box, and the orientation in the final rendered image is
also affected by the axes limits.
Most of the code below is used for adding labels and informative
text to the plots. The described effects of *origin* and *extent* can be
seen in the plots without the need to follow all code details.
For a quick understanding, you may want to skip the code details below and
directly continue with the discussion of the results.
``` python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
def index_to_coordinate(index, extent, origin):
"""Return the pixel center of an index."""
left, right, bottom, top = extent
hshift = 0.5 * np.sign(right - left)
left, right = left + hshift, right - hshift
vshift = 0.5 * np.sign(top - bottom)
bottom, top = bottom + vshift, top - vshift
if origin == 'upper':
bottom, top = top, bottom
return {
"[0, 0]": (left, bottom),
"[M', 0]": (left, top),
"[0, N']": (right, bottom),
"[M', N']": (right, top),
}[index]
def get_index_label_pos(index, extent, origin, inverted_xindex):
"""
Return the desired position and horizontal alignment of an index label.
"""
if extent is None:
extent = lookup_extent(origin)
left, right, bottom, top = extent
x, y = index_to_coordinate(index, extent, origin)
is_x0 = index[-2:] == "0]"
halign = 'left' if is_x0 ^ inverted_xindex else 'right'
hshift = 0.5 * np.sign(left - right)
x += hshift * (1 if is_x0 else -1)
return x, y, halign
def get_color(index, data, cmap):
"""Return the data color of an index."""
val = {
"[0, 0]": data[0, 0],
"[0, N']": data[0, -1],
"[M', 0]": data[-1, 0],
"[M', N']": data[-1, -1],
}[index]
return cmap(val / data.max())
def lookup_extent(origin):
"""Return extent for label positioning when not given explicitly."""
if origin == 'lower':
return (-0.5, 6.5, -0.5, 5.5)
else:
return (-0.5, 6.5, 5.5, -0.5)
def set_extent_None_text(ax):
ax.text(3, 2.5, 'equals\nextent=None', size='large',
ha='center', va='center', color='w')
def plot_imshow_with_labels(ax, data, extent, origin, xlim, ylim):
"""Actually run ``imshow()`` and add extent and index labels."""
im = ax.imshow(data, origin=origin, extent=extent)
# extent labels (left, right, bottom, top)
left, right, bottom, top = im.get_extent()
if xlim is None or top > bottom:
upper_string, lower_string = 'top', 'bottom'
else:
upper_string, lower_string = 'bottom', 'top'
if ylim is None or left < right:
port_string, starboard_string = 'left', 'right'
inverted_xindex = False
else:
port_string, starboard_string = 'right', 'left'
inverted_xindex = True
bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"}
ann_kwargs = {'xycoords': 'axes fraction',
'textcoords': 'offset points',
'bbox': bbox_kwargs}
ax.annotate(upper_string, xy=(.5, 1), xytext=(0, -1),
ha='center', va='top', **ann_kwargs)
ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1),
ha='center', va='bottom', **ann_kwargs)
ax.annotate(port_string, xy=(0, .5), xytext=(1, 0),
ha='left', va='center', rotation=90,
**ann_kwargs)
ax.annotate(starboard_string, xy=(1, .5), xytext=(-1, 0),
ha='right', va='center', rotation=-90,
**ann_kwargs)
ax.set_title('origin: {origin}'.format(origin=origin))
# index labels
for index in ["[0, 0]", "[0, N']", "[M', 0]", "[M', N']"]:
tx, ty, halign = get_index_label_pos(index, extent, origin,
inverted_xindex)
facecolor = get_color(index, data, im.get_cmap())
ax.text(tx, ty, index, color='white', ha=halign, va='center',
bbox={'boxstyle': 'square', 'facecolor': facecolor})
if xlim:
ax.set_xlim(*xlim)
if ylim:
ax.set_ylim(*ylim)
def generate_imshow_demo_grid(extents, xlim=None, ylim=None):
N = len(extents)
fig = plt.figure(tight_layout=True)
fig.set_size_inches(6, N * (11.25) / 5)
gs = GridSpec(N, 5, figure=fig)
columns = {'label': [fig.add_subplot(gs[j, 0]) for j in range(N)],
'upper': [fig.add_subplot(gs[j, 1:3]) for j in range(N)],
'lower': [fig.add_subplot(gs[j, 3:5]) for j in range(N)]}
x, y = np.ogrid[0:6, 0:7]
data = x + y
for origin in ['upper', 'lower']:
for ax, extent in zip(columns[origin], extents):
plot_imshow_with_labels(ax, data, extent, origin, xlim, ylim)
for ax, extent in zip(columns['label'], extents):
text_kwargs = {'ha': 'right',
'va': 'center',
'xycoords': 'axes fraction',
'xy': (1, .5)}
if extent is None:
ax.annotate('None', **text_kwargs)
ax.set_title('extent=')
else:
left, right, bottom, top = extent
text = ('left: {left:0.1f}\nright: {right:0.1f}\n' +
'bottom: {bottom:0.1f}\ntop: {top:0.1f}\n').format(
left=left, right=right, bottom=bottom, top=top)
ax.annotate(text, **text_kwargs)
ax.axis('off')
return columns
```
## Default extent
First, let's have a look at the default ``extent=None``
``` python
generate_imshow_demo_grid(extents=[None])
```
![sphx_glr_imshow_extent_001](https://matplotlib.org/_images/sphx_glr_imshow_extent_001.png)
Generally, for an array of shape (M, N), the first index runs along the
vertical, the second index runs along the horizontal.
The pixel centers are at integer positions ranging from 0 to ``N' = N - 1``
horizontally and from 0 to ``M' = M - 1`` vertically.
*origin* determines how to the data is filled in the bounding box.
For ``origin='lower'``:
- [0, 0] is at (left, bottom)
- [M', 0] is at (left, top)
- [0, N'] is at (right, bottom)
- [M', N'] is at (right, top)
``origin='upper'`` reverses the vertical axes direction and filling:
- [0, 0] is at (left, top)
- [M', 0] is at (left, bottom)
- [0, N'] is at (right, top)
- [M', N'] is at (right, bottom)
In summary, the position of the [0, 0] index as well as the extent are
influenced by *origin*:
---
origin
[0, 0] position
extent
upper
top left
(-0.5, numcols-0.5, numrows-0.5, -0.5)
lower
bottom left
(-0.5, numcols-0.5, -0.5, numrows-0.5)
The default value of *origin* is set by ``rcParams["image.origin"] = 'upper'`` which defaults
to ``'upper'`` to match the matrix indexing conventions in math and
computer graphics image indexing conventions.
## Explicit extent
By setting *extent* we define the coordinates of the image area. The
underlying image data is interpolated/resampled to fill that area.
If the axes is set to autoscale, then the view limits of the axes are set
to match the *extent* which ensures that the coordinate set by
``(left, bottom)`` is at the bottom left of the axes! However, this
may invert the axis so they do not increase in the 'natural' direction.
``` python
extents = [(-0.5, 6.5, -0.5, 5.5),
(-0.5, 6.5, 5.5, -0.5),
(6.5, -0.5, -0.5, 5.5),
(6.5, -0.5, 5.5, -0.5)]
columns = generate_imshow_demo_grid(extents)
set_extent_None_text(columns['upper'][1])
set_extent_None_text(columns['lower'][0])
```
![sphx_glr_imshow_extent_002](https://matplotlib.org/_images/sphx_glr_imshow_extent_002.png)
## Explicit extent and axes limits
If we fix the axes limits by explicitly setting ``set_xlim`` / ``set_ylim``, we
force a certain size and orientation of the axes.
This can decouple the 'left-right' and 'top-bottom' sense of the image from
the orientation on the screen.
In the example below we have chosen the limits slightly larger than the
extent (note the white areas within the Axes).
While we keep the extents as in the examples before, the coordinate (0, 0)
is now explicitly put at the bottom left and values increase to up and to
the right (from the viewer point of view).
We can see that:
- The coordinate ``(left, bottom)`` anchors the image which then fills the
box going towards the ``(right, top)`` point in data space.
- The first column is always closest to the 'left'.
- *origin* controls if the first row is closest to 'top' or 'bottom'.
- The image may be inverted along either direction.
- The 'left-right' and 'top-bottom' sense of the image may be uncoupled from
the orientation on the screen.
``` python
generate_imshow_demo_grid(extents=[None] + extents,
xlim=(-2, 8), ylim=(-1, 6))
```
![sphx_glr_imshow_extent_003](https://matplotlib.org/_images/sphx_glr_imshow_extent_003.png)
**Total running time of the script:** ( 0 minutes 2.056 seconds)
## Download
- [Download Python source code: imshow_extent.py](https://matplotlib.org/_downloads/1b073a3f2fab4eae80964340b65629bc/imshow_extent.py)
- [Download Jupyter notebook: imshow_extent.ipynb](https://matplotlib.org/_downloads/e7e77a6502f9e28a843cccc17c2dfd89/imshow_extent.ipynb)

View File

@@ -0,0 +1,314 @@
---
sidebarDepth: 3
sidebar: auto
---
# Legend guide
Generating legends flexibly in Matplotlib.
This legend guide is an extension of the documentation available at
[``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend) - please ensure you are familiar with
contents of that documentation before proceeding with this guide.
This guide makes use of some common terms, which are documented here for clarity:
legend entry
legend key
legend label
legend handle
## Controlling the legend entries
Calling [``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend) with no arguments automatically fetches the legend
handles and their associated labels. This functionality is equivalent to:
``` python
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels)
```
The [``get_legend_handles_labels()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.get_legend_handles_labels.html#matplotlib.axes.Axes.get_legend_handles_labels) function returns
a list of handles/artists which exist on the Axes which can be used to
generate entries for the resulting legend - it is worth noting however that
not all artists can be added to a legend, at which point a "proxy" will have
to be created (see [Creating artists specifically for adding to the legend (aka. Proxy artists)](#proxy-legend-handles) for further details).
For full control of what is being added to the legend, it is common to pass
the appropriate handles directly to [``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend):
``` python
line_up, = plt.plot([1,2,3], label='Line 2')
line_down, = plt.plot([3,2,1], label='Line 1')
plt.legend(handles=[line_up, line_down])
```
In some cases, it is not possible to set the label of the handle, so it is
possible to pass through the list of labels to [``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend):
``` python
line_up, = plt.plot([1,2,3], label='Line 2')
line_down, = plt.plot([3,2,1], label='Line 1')
plt.legend([line_up, line_down], ['Line Up', 'Line Down'])
```
## Creating artists specifically for adding to the legend (aka. Proxy artists)
Not all handles can be turned into legend entries automatically,
so it is often necessary to create an artist which *can*. Legend handles
don't have to exists on the Figure or Axes in order to be used.
Suppose we wanted to create a legend which has an entry for some data which
is represented by a red color:
``` python
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
red_patch = mpatches.Patch(color='red', label='The red data')
plt.legend(handles=[red_patch])
plt.show()
```
![sphx_glr_legend_guide_001](https://matplotlib.org/_images/sphx_glr_legend_guide_001.png)
There are many supported legend handles, instead of creating a patch of color
we could have created a line with a marker:
``` python
import matplotlib.lines as mlines
blue_line = mlines.Line2D([], [], color='blue', marker='*',
markersize=15, label='Blue stars')
plt.legend(handles=[blue_line])
plt.show()
```
![sphx_glr_legend_guide_002](https://matplotlib.org/_images/sphx_glr_legend_guide_002.png)
## Legend location
The location of the legend can be specified by the keyword argument
*loc*. Please see the documentation at [``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend) for more details.
The ``bbox_to_anchor`` keyword gives a great degree of control for manual
legend placement. For example, if you want your axes legend located at the
figure's top right-hand corner instead of the axes' corner, simply specify
the corner's location, and the coordinate system of that location:
``` python
plt.legend(bbox_to_anchor=(1, 1),
bbox_transform=plt.gcf().transFigure)
```
More examples of custom legend placement:
``` python
plt.subplot(211)
plt.plot([1, 2, 3], label="test1")
plt.plot([3, 2, 1], label="test2")
# Place a legend above this subplot, expanding itself to
# fully use the given bounding box.
plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left',
ncol=2, mode="expand", borderaxespad=0.)
plt.subplot(223)
plt.plot([1, 2, 3], label="test1")
plt.plot([3, 2, 1], label="test2")
# Place a legend to the right of this smaller subplot.
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
plt.show()
```
![sphx_glr_legend_guide_003](https://matplotlib.org/_images/sphx_glr_legend_guide_003.png)
## Multiple legends on the same Axes
Sometimes it is more clear to split legend entries across multiple
legends. Whilst the instinctive approach to doing this might be to call
the [``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend) function multiple times, you will find that only one
legend ever exists on the Axes. This has been done so that it is possible
to call [``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend) repeatedly to update the legend to the latest
handles on the Axes, so to persist old legend instances, we must add them
manually to the Axes:
``` python
line1, = plt.plot([1, 2, 3], label="Line 1", linestyle='--')
line2, = plt.plot([3, 2, 1], label="Line 2", linewidth=4)
# Create a legend for the first line.
first_legend = plt.legend(handles=[line1], loc='upper right')
# Add the legend manually to the current Axes.
ax = plt.gca().add_artist(first_legend)
# Create another legend for the second line.
plt.legend(handles=[line2], loc='lower right')
plt.show()
```
![sphx_glr_legend_guide_004](https://matplotlib.org/_images/sphx_glr_legend_guide_004.png)
## Legend Handlers
In order to create legend entries, handles are given as an argument to an
appropriate [``HandlerBase``](https://matplotlib.orgapi/legend_handler_api.html#matplotlib.legend_handler.HandlerBase) subclass.
The choice of handler subclass is determined by the following rules:
1. Update [``get_legend_handler_map()``](https://matplotlib.orgapi/legend_api.html#matplotlib.legend.Legend.get_legend_handler_map)
with the value in the ``handler_map`` keyword.
1. Check if the ``handle`` is in the newly created ``handler_map``.
1. Check if the type of ``handle`` is in the newly created
``handler_map``.
1. Check if any of the types in the ``handle``'s mro is in the newly
created ``handler_map``.
For completeness, this logic is mostly implemented in
[``get_legend_handler()``](https://matplotlib.orgapi/legend_api.html#matplotlib.legend.Legend.get_legend_handler).
All of this flexibility means that we have the necessary hooks to implement
custom handlers for our own type of legend key.
The simplest example of using custom handlers is to instantiate one of the
existing [``HandlerBase``](https://matplotlib.orgapi/legend_handler_api.html#matplotlib.legend_handler.HandlerBase) subclasses. For the
sake of simplicity, let's choose [``matplotlib.legend_handler.HandlerLine2D``](https://matplotlib.orgapi/legend_handler_api.html#matplotlib.legend_handler.HandlerLine2D)
which accepts a ``numpoints`` argument (note numpoints is a keyword
on the [``legend()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend) function for convenience). We can then pass the mapping
of instance to Handler as a keyword to legend.
``` python
from matplotlib.legend_handler import HandlerLine2D
line1, = plt.plot([3, 2, 1], marker='o', label='Line 1')
line2, = plt.plot([1, 2, 3], marker='o', label='Line 2')
plt.legend(handler_map={line1: HandlerLine2D(numpoints=4)})
```
![sphx_glr_legend_guide_005](https://matplotlib.org/_images/sphx_glr_legend_guide_005.png)
As you can see, "Line 1" now has 4 marker points, where "Line 2" has 2 (the
default). Try the above code, only change the map's key from ``line1`` to
``type(line1)``. Notice how now both [``Line2D``](https://matplotlib.orgapi/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D) instances
get 4 markers.
Along with handlers for complex plot types such as errorbars, stem plots
and histograms, the default ``handler_map`` has a special ``tuple`` handler
([``HandlerTuple``](https://matplotlib.orgapi/legend_handler_api.html#matplotlib.legend_handler.HandlerTuple)) which simply plots
the handles on top of one another for each item in the given tuple. The
following example demonstrates combining two legend keys on top of one another:
``` python
from numpy.random import randn
z = randn(10)
red_dot, = plt.plot(z, "ro", markersize=15)
# Put a white cross over some of the data.
white_cross, = plt.plot(z[:5], "w+", markeredgewidth=3, markersize=15)
plt.legend([red_dot, (red_dot, white_cross)], ["Attr A", "Attr A+B"])
```
![sphx_glr_legend_guide_006](https://matplotlib.org/_images/sphx_glr_legend_guide_006.png)
The [``HandlerTuple``](https://matplotlib.orgapi/legend_handler_api.html#matplotlib.legend_handler.HandlerTuple) class can also be used to
assign several legend keys to the same entry:
``` python
from matplotlib.legend_handler import HandlerLine2D, HandlerTuple
p1, = plt.plot([1, 2.5, 3], 'r-d')
p2, = plt.plot([3, 2, 1], 'k-o')
l = plt.legend([(p1, p2)], ['Two keys'], numpoints=1,
handler_map={tuple: HandlerTuple(ndivide=None)})
```
![sphx_glr_legend_guide_007](https://matplotlib.org/_images/sphx_glr_legend_guide_007.png)
### Implementing a custom legend handler
A custom handler can be implemented to turn any handle into a legend key (handles
don't necessarily need to be matplotlib artists).
The handler must implement a "legend_artist" method which returns a
single artist for the legend to use. Signature details about the "legend_artist"
are documented at [``legend_artist()``](https://matplotlib.orgapi/legend_handler_api.html#matplotlib.legend_handler.HandlerBase.legend_artist).
``` python
import matplotlib.patches as mpatches
class AnyObject(object):
pass
class AnyObjectHandler(object):
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
patch = mpatches.Rectangle([x0, y0], width, height, facecolor='red',
edgecolor='black', hatch='xx', lw=3,
transform=handlebox.get_transform())
handlebox.add_artist(patch)
return patch
plt.legend([AnyObject()], ['My first handler'],
handler_map={AnyObject: AnyObjectHandler()})
```
![sphx_glr_legend_guide_008](https://matplotlib.org/_images/sphx_glr_legend_guide_008.png)
Alternatively, had we wanted to globally accept ``AnyObject`` instances without
needing to manually set the ``handler_map`` keyword all the time, we could have
registered the new handler with:
``` python
from matplotlib.legend import Legend
Legend.update_default_handler_map({AnyObject: AnyObjectHandler()})
```
Whilst the power here is clear, remember that there are already many handlers
implemented and what you want to achieve may already be easily possible with
existing classes. For example, to produce elliptical legend keys, rather than
rectangular ones:
``` python
from matplotlib.legend_handler import HandlerPatch
class HandlerEllipse(HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
p = mpatches.Ellipse(xy=center, width=width + xdescent,
height=height + ydescent)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]
c = mpatches.Circle((0.5, 0.5), 0.25, facecolor="green",
edgecolor="red", linewidth=3)
plt.gca().add_patch(c)
plt.legend([c], ["An ellipse, not a rectangle"],
handler_map={mpatches.Circle: HandlerEllipse()})
```
![sphx_glr_legend_guide_009](https://matplotlib.org/_images/sphx_glr_legend_guide_009.png)
## Download
- [Download Python source code: legend_guide.py](https://matplotlib.org/_downloads/65714cd51723c032709ddf40dd43e3cf/legend_guide.py)
- [Download Jupyter notebook: legend_guide.ipynb](https://matplotlib.org/_downloads/89b61becf2e0c701373e39916f7b5428/legend_guide.ipynb)

View File

@@ -0,0 +1,418 @@
---
sidebarDepth: 3
sidebar: auto
---
# Tight Layout guide
How to use tight-layout to fit plots within your figure cleanly.
*tight_layout* automatically adjusts subplot params so that the
subplot(s) fits in to the figure area. This is an experimental
feature and may not work for some cases. It only checks the extents
of ticklabels, axis labels, and titles.
An alternative to *tight_layout* is [constrained_layout](constrainedlayout_guide.html).
## Simple Example
In matplotlib, the location of axes (including subplots) are specified in
normalized figure coordinates. It can happen that your axis labels or
titles (or sometimes even ticklabels) go outside the figure area, and are thus
clipped.
``` python
# sphinx_gallery_thumbnail_number = 7
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['savefig.facecolor'] = "0.8"
def example_plot(ax, fontsize=12):
ax.plot([1, 2])
ax.locator_params(nbins=3)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)
plt.close('all')
fig, ax = plt.subplots()
example_plot(ax, fontsize=24)
```
![sphx_glr_tight_layout_guide_001](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_001.png)
To prevent this, the location of axes needs to be adjusted. For
subplots, this can be done by adjusting the subplot params
([Move the edge of an axes to make room for tick labels](https://matplotlib.orgfaq/howto_faq.html#howto-subplots-adjust)). Matplotlib v1.1 introduces a new
command [``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) that does this
automatically for you.
``` python
fig, ax = plt.subplots()
example_plot(ax, fontsize=24)
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_002](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_002.png)
Note that [``matplotlib.pyplot.tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) will only adjust the
subplot params when it is called. In order to perform this adjustment each
time the figure is redrawn, you can call ``fig.set_tight_layout(True)``, or,
equivalently, set the ``figure.autolayout`` rcParam to ``True``.
When you have multiple subplots, often you see labels of different
axes overlapping each other.
``` python
plt.close('all')
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
```
![sphx_glr_tight_layout_guide_003](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_003.png)
[``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) will also adjust spacing between
subplots to minimize the overlaps.
``` python
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_004](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_004.png)
[``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) can take keyword arguments of
*pad*, *w_pad* and *h_pad*. These control the extra padding around the
figure border and between subplots. The pads are specified in fraction
of fontsize.
``` python
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)
```
![sphx_glr_tight_layout_guide_005](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_005.png)
[``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) will work even if the sizes of
subplots are different as far as their grid specification is
compatible. In the example below, *ax1* and *ax2* are subplots of a 2x2
grid, while *ax3* is of a 1x2 grid.
``` python
plt.close('all')
fig = plt.figure()
ax1 = plt.subplot(221)
ax2 = plt.subplot(223)
ax3 = plt.subplot(122)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_006](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_006.png)
It works with subplots created with
[``subplot2grid()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.subplot2grid.html#matplotlib.pyplot.subplot2grid). In general, subplots created
from the gridspec ([Customizing Figure Layouts Using GridSpec and Other Functions](gridspec.html)) will work.
``` python
plt.close('all')
fig = plt.figure()
ax1 = plt.subplot2grid((3, 3), (0, 0))
ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_007](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_007.png)
Although not thoroughly tested, it seems to work for subplots with
aspect != "auto" (e.g., axes with images).
``` python
arr = np.arange(100).reshape((10, 10))
plt.close('all')
fig = plt.figure(figsize=(5, 4))
ax = plt.subplot(111)
im = ax.imshow(arr, interpolation="none")
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_008](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_008.png)
## Caveats
- [``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) only considers ticklabels, axis
labels, and titles. Thus, other artists may be clipped and also may
overlap.
- It assumes that the extra space needed for ticklabels, axis labels,
and titles is independent of original location of axes. This is
often true, but there are rare cases where it is not.
- pad=0 clips some of the texts by a few pixels. This may be a bug or
a limitation of the current algorithm and it is not clear why it
happens. Meanwhile, use of pad at least larger than 0.3 is
recommended.
## Use with GridSpec
GridSpec has its own [``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec.tight_layout) method
(the pyplot api [``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) also works).
``` python
import matplotlib.gridspec as gridspec
plt.close('all')
fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs1.tight_layout(fig)
```
![sphx_glr_tight_layout_guide_009](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_009.png)
You may provide an optional *rect* parameter, which specifies the bounding box
that the subplots will be fit inside. The coordinates must be in normalized
figure coordinates and the default is (0, 0, 1, 1).
``` python
fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs1.tight_layout(fig, rect=[0, 0, 0.5, 1])
```
![sphx_glr_tight_layout_guide_010](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_010.png)
For example, this can be used for a figure with multiple gridspecs.
``` python
fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs1.tight_layout(fig, rect=[0, 0, 0.5, 1])
gs2 = gridspec.GridSpec(3, 1)
for ss in gs2:
ax = fig.add_subplot(ss)
example_plot(ax)
ax.set_title("")
ax.set_xlabel("")
ax.set_xlabel("x-label", fontsize=12)
gs2.tight_layout(fig, rect=[0.5, 0, 1, 1], h_pad=0.5)
# We may try to match the top and bottom of two grids ::
top = min(gs1.top, gs2.top)
bottom = max(gs1.bottom, gs2.bottom)
gs1.update(top=top, bottom=bottom)
gs2.update(top=top, bottom=bottom)
plt.show()
```
![sphx_glr_tight_layout_guide_011](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_011.png)
While this should be mostly good enough, adjusting top and bottom
may require adjustment of hspace also. To update hspace & vspace, we
call [``tight_layout()``](https://matplotlib.orgapi/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec.tight_layout) again with updated
rect argument. Note that the rect argument specifies the area including the
ticklabels, etc. Thus, we will increase the bottom (which is 0 for the normal
case) by the difference between the *bottom* from above and the bottom of each
gridspec. Same thing for the top.
``` python
fig = plt.gcf()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot(ax2)
gs1.tight_layout(fig, rect=[0, 0, 0.5, 1])
gs2 = gridspec.GridSpec(3, 1)
for ss in gs2:
ax = fig.add_subplot(ss)
example_plot(ax)
ax.set_title("")
ax.set_xlabel("")
ax.set_xlabel("x-label", fontsize=12)
gs2.tight_layout(fig, rect=[0.5, 0, 1, 1], h_pad=0.5)
top = min(gs1.top, gs2.top)
bottom = max(gs1.bottom, gs2.bottom)
gs1.update(top=top, bottom=bottom)
gs2.update(top=top, bottom=bottom)
top = min(gs1.top, gs2.top)
bottom = max(gs1.bottom, gs2.bottom)
gs1.tight_layout(fig, rect=[None, 0 + (bottom-gs1.bottom),
0.5, 1 - (gs1.top-top)])
gs2.tight_layout(fig, rect=[0.5, 0 + (bottom-gs2.bottom),
None, 1 - (gs2.top-top)],
h_pad=0.5)
```
![sphx_glr_tight_layout_guide_012](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_012.png)
## Legends and Annotations
Pre Matplotlib 2.2, legends and annotations were excluded from the bounding
box calculations that decide the layout. Subsequently these artists were
added to the calculation, but sometimes it is undesirable to include them.
For instance in this case it might be good to have the axes shring a bit
to make room for the legend:
``` python
fig, ax = plt.subplots(figsize=(4, 3))
lines = ax.plot(range(10), label='A simple plot')
ax.legend(bbox_to_anchor=(0.7, 0.5), loc='center left',)
fig.tight_layout()
plt.show()
```
![sphx_glr_tight_layout_guide_013](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_013.png)
However, sometimes this is not desired (quite often when using
``fig.savefig('outname.png', bbox_inches='tight')``). In order to
remove the legend from the bounding box calculation, we simply set its
bounding ``leg.set_in_layout(False)`` and the legend will be ignored.
``` python
fig, ax = plt.subplots(figsize=(4, 3))
lines = ax.plot(range(10), label='B simple plot')
leg = ax.legend(bbox_to_anchor=(0.7, 0.5), loc='center left',)
leg.set_in_layout(False)
fig.tight_layout()
plt.show()
```
![sphx_glr_tight_layout_guide_014](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_014.png)
## Use with AxesGrid1
While limited, the axes_grid1 toolkit is also supported.
``` python
from mpl_toolkits.axes_grid1 import Grid
plt.close('all')
fig = plt.figure()
grid = Grid(fig, rect=111, nrows_ncols=(2, 2),
axes_pad=0.25, label_mode='L',
)
for ax in grid:
example_plot(ax)
ax.title.set_visible(False)
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_015](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_015.png)
## Colorbar
If you create a colorbar with the [``colorbar()``](https://matplotlib.orgapi/_as_gen/matplotlib.pyplot.colorbar.html#matplotlib.pyplot.colorbar)
command, the created colorbar is an instance of Axes, *not* Subplot, so
tight_layout does not work. With Matplotlib v1.1, you may create a
colorbar as a subplot using the gridspec.
``` python
plt.close('all')
arr = np.arange(100).reshape((10, 10))
fig = plt.figure(figsize=(4, 4))
im = plt.imshow(arr, interpolation="none")
plt.colorbar(im, use_gridspec=True)
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_016](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_016.png)
Another option is to use AxesGrid1 toolkit to
explicitly create an axes for colorbar.
``` python
from mpl_toolkits.axes_grid1 import make_axes_locatable
plt.close('all')
arr = np.arange(100).reshape((10, 10))
fig = plt.figure(figsize=(4, 4))
im = plt.imshow(arr, interpolation="none")
divider = make_axes_locatable(plt.gca())
cax = divider.append_axes("right", "5%", pad="3%")
plt.colorbar(im, cax=cax)
plt.tight_layout()
```
![sphx_glr_tight_layout_guide_017](https://matplotlib.org/_images/sphx_glr_tight_layout_guide_017.png)
**Total running time of the script:** ( 0 minutes 3.417 seconds)
## Download
- [Download Python source code: tight_layout_guide.py](https://matplotlib.org/_downloads/08204f760ca1d178acca434333c21c5c/tight_layout_guide.py)
- [Download Jupyter notebook: tight_layout_guide.ipynb](https://matplotlib.org/_downloads/967ce4dd04ce9628b993a5a4e402046a/tight_layout_guide.ipynb)