It's true that matplotlib is a fantastic visualizing tool in Python. But it's also true that tweaking details in matplotlib is a real pain. You may easily lose hours to find out how to change a small part of your plot. Sometimes you even don't know the name of the part, which makes googling it harder. Even if you find a hint on Stack Overflow, you may spend another couple of hours to make it fit your case. These non-rewarding tasks can be avoided by knowing what a figure in matplotlib consists of and what you can do with them. Like most of you, I think, I have overcome my plotting problems by reading lots of answers by matplotlib gurus on Stack Overflow. Recently, I noticed that an official tutorial about Artist
objects is very informative and helpful to understand what is going on when we plot with matplotlib and to reduce a large amount of time spent for tweaking1. In this post, I would like to share some basic knowledge about Artist
objects in matplotlib which would prevent you from spending hours for tweaking.
Purpose of this post
I'm not going to write about how-tos like "do like this when you want to do this", but a basic concept of Artist
in matplotlib which helps you choose suitable search queries and arrange a solution for a similar problem as yours. After reading this, you'll probably understand those huge amount of recipes on the Internet more clearly. This also applies to those who use seaborn and plotting features of pandas which are wrappers of matplotlib.
Contents
This post is basically an English version of the original article I wrote in Japanese, and is mostly based on Artist tutorial and Usage Guide (2.1.1 at the time of publication of the original one)
Who is this for?
Matplotlib users who
- are able to make plots if needed, but often struggle to make them appropriate for publication or presentation (and are irritated by "the last one mile" to what you really want).
- have successfully found an exact solution on Stack Overflow but are still hazy about how it works and cannot apply it to other problems.
- found multiple hints for a problem but are not sure which to follow.
Environment
- Python 3.6
- matplotlib 2.2
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
plt.show()
is omitted in this article because I use Jupyter notebook's inline plot.
Two plotting styles you should be aware of
Before looking into Artist
objects, I would like to mention the difference between plt.plot
and ax.plot
, or Pyplot and object-oriented API. While object-oriented API style is officially recommended, there are still lots of examples and code snippets using Pyplot style, including official docs. Some are even mixing both styles meaninglessly, which causes unnecessary confusion for beginners. Since official doc has good notes about them such as A note on the Object-Oriented API vs Pyplot and Coding Styles, here I only make some comments on them. If you look for introduction for them, I recommend official tutorials.
Object-oriented API interface
This is the recommended style which often starts with fig, ax = plt.subplots()
or other equivalents followed by ax.plot
, ax.imshow
etc. fig
and ax
are, in fact, Artist
s. Here are some simplest examples.
fig, ax = plt.subplots()
ax.plot(x,y)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(x, y)
Some tutorials use fig = plt.gcf()
and ax = plt.gca()
. These should be used when you switch from Pyplot interface to OO interface, but some Pyplot-based codes include, for example, pointless ax = plt.gca()
which is apparently copied from OO-based code without understanding. Using plt.gcf()
or plt.gca()
isn't a bad thing at all if one switch the interface intentionally. Considering implicit switching can be a cause of confusion for beginners, using plt.subplots
or fig.add_subplot
from the beginning would be the best practice for most of the cases if they are publicly available.
Pyplot interface
This is a MATLAB-user-friendly style in which everything is done with plt.***
.
# https://matplotlib.org/tutorials/introductory/pyplot.html
def f(t):
return np.exp(-t) * np.cos(2*np.pi*t)
t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)
plt.figure(1)
plt.subplot(211)
plt.plot(t1, f(t1), 'bo', t2, f(t2), 'k')
plt.subplot(212)
plt.plot(t2, np.cos(2*np.pi*t2), 'r--')
plt.show()
At first, it seems very simple because there is no need to think about which objects you are handling. You only need to know which "state" you are in, which is why this style is also called "stateful interface". Here, a "state" means which figure and subplot you are currently in. As you see in Pyplot tutorial, it gives a nice figure if your plot is not so complicated. Although Pyplot interface offers lots of functions to change plot settings, you may reach its limit within a couple of hours, days, months (or never if you are lucky enough) depending on what you want to do. At this stage, you need to switch to OO interface. That is why I recommend to use OO interface from the beginning. But Pyplot is still useful for quick checks or any occasions where you need rough plots.
The hierarchy in matplotlib
After googling several times, you will notice that matplotlib has a hierarchical structure consisting of something often called fig
and ax
. Old doc for matplotlib 1.5 has a nice image explaining this.
Actually, these three components are special Artist
s called "containers" (and there is the fourth container Tick
) which we will see later. This hierarchy makes simple examples above even clearer.
fig, ax = plt.subplots() # make Figure and Axes which belongs to 'fig'
fig = plt.figure() # make Figure
ax = fig.add_subplot(1,1,1) # make Axes belonging to fig
Taking a further look at attributes of fig
and ax
helps you understand the hierarchy more.
fig = plt.figure()
ax = fig.add_subplot(1,1,1) # make a blank plotting area
print('fig.axes:', fig.axes)
print('ax.figure:', ax.figure)
print('ax.xaxis:', ax.xaxis)
print('ax.yaxis:', ax.yaxis)
print('ax.xaxis.axes:', ax.xaxis.axes)
print('ax.yaxis.axes:', ax.yaxis.axes)
print('ax.xaxis.figure:', ax.xaxis.figure)
print('ax.yaxis.figure:', ax.yaxis.figure)
print('fig.xaxis:', fig.xaxis)
fig.axes: [<matplotlib.axes._subplots.AxesSubplot object at 0x1167b0630>]
ax.figure: Figure(432x288)
ax.xaxis: XAxis(54.000000,36.000000)
ax.yaxis: YAxis(54.000000,36.000000)
ax.xaxis.axes: AxesSubplot(0.125,0.125;0.775x0.755)
ax.yaxis.axes: AxesSubplot(0.125,0.125;0.775x0.755)
ax.xaxis.figure: Figure(432x288)
ax.yaxis.figure: Figure(432x288)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-21-b9f2d5d9fe09> in <module>()
9 print('ax.xaxis.figure:', ax.xaxis.figure)
10 print('ax.yaxis.figure:', ax.yaxis.figure)
---> 11 print('fig.xaxis:', fig.xaxis)
AttributeError: 'Figure' object has no attribute 'xaxis'
From these results, we can expect following rules about the hierarchy of Figure
, Axes
, and Axis
.
-
Figure
knowsAxes
but notAxis
. -
Axes
knowsFigure
andAxis
both. -
Axis
knowsAxes
andFigure
both. -
Figure
can contain multipleAxes
becausefig.axes
is a list ofAxes
. -
Axes
can belong to only singleFigure
becauseax.figure
is not a list. -
Axes
can have oneXAxis
andYAxis
respectively for similar reason. -
XAxis
andYAxis
can belong to singleAxes
and, accordingly, singleFigure
.
Everything in your plot is an Artist
Instead of a figure explaining the hierarchical structure, Usage Guide in the current doc has "Anatomy of a figure"2 explaining all components in a figure which is also informative3.
From lines and points which represent data to minor ticks and text labels for them on X axis, every single component in a figure is an Artist
object4. There are two types of Artist
, containers and primitives. As I wrote in the previous section, three components in matplotlib's hierarchy, Figure
, Axes
, and Axis
are containers which can contain lower containers and multiple primitives such as Line2D
made by ax.plot
, PathCollection
by ax.scatter
, or Text
by ax.annotate
. Even tick lines and labels are in fact Line2D
and Text
which belong to the fourth container Tick
.
Containers have many "boxes" (Python lists, technicaly) for each type of primitives. For example, an Axes
object, ax
, just after instantation has an empty list ax.lines
. Frequently-used command ax.plot
adds a Line2D
object in the list and does other accompanying settings silently.
x = np.linspace(0, 2*np.pi, 100)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
print('ax.lines before plot:\n', ax.lines) # empty
line1, = ax.plot(x, np.sin(x), label='1st plot') # add Line2D in ax.lines
print('ax.lines after 1st plot:\n', ax.lines)
line2, = ax.plot(x, np.sin(x+np.pi/8), label='2nd plot') # add another Line2D
print('ax.lines after 2nd plot:\n', ax.lines)
ax.legend()
print('line1:', line1)
print('line2:', line2)
ax.lines before plot:
[]
ax.lines after 1st plot:
[<matplotlib.lines.Line2D object at 0x1171ca748>]
ax.lines after 2nd plot:
[<matplotlib.lines.Line2D object at 0x1171ca748>, <matplotlib.lines.Line2D object at 0x117430550>]
line1: Line2D(1st plot)
line2: Line2D(2nd plot)
Following sections summarize four containers. Tables are copied from Artist tutorial.
Figure
Figure attributes |
description |
---|---|
fig.axes |
A list of Axes instances (includes Subplot ) |
fig.patch |
The Rectangle background |
fig.images |
A list of FigureImages patches - useful for raw pixel display |
fig.legends |
A list of Figure Legend instances (different from Axes.legends ) |
fig.lines |
A list of Figure Line2D instances (rarely used, see Axes.lines ) |
fig.patches |
A list of Figure patches (rarely used, see Axes.patches ) |
fig.texts |
A list Figure Text instances |
Attributes with a plural name are lists and those with a singular name represent a single object. It is worth noting that Artist
s belonging to Figure
use Figure
coordinate by default. This can be converted to Axes
or data coordinates by using Transforms
, which is outside the scope of this post.
fig.legend
and ax.legend
fig.legends
is a "box" for legends added by fig.lenged
method. You may think "What's that for? We have ax.legend
." The difference is a scope of each method. While ax.legend
only collects labels from Artist
s belonging to ax
, fig.legend
gathers labels from all Axes
under fig
. This is useful, for instance, when you make a plot using ax.twinx
. Simply using ax.legend
twice makes two legends, which is not desirable in general.
x = np.linspace(0, 2*np.pi, 100)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='sin(x)')
ax1 = ax.twinx()
ax1.plot(x, 2*np.cos(x), c='C1', label='2*cos(x)')
# cf. 'CN' notation
# https://matplotlib.org/tutorials/colors/colors.html#cn-color-selection
ax.legend()
ax1.legend()
A famous recipe to put them together is to combine legend handlers and labels of both Axes
.
# Executing this part in a different notebook cell shows an updated figure.
handler, label = ax.get_legend_handles_labels()
handler1, label1 = ax1.get_legend_handles_labels()
ax.legend(handler+handler1, label+label1, loc='upper center', title='ax.legend')
# Legend made by ax1.legend remains
fig
This can be easily done by fig.legend
without arguments which was introduced in ver 2.15. By default, the position is specified with Figure
coordinate which is not useful when you want to put it in a plotting frame. You can change it to Axes
coordinate with bbox_transform
keyword.
fig.legend(loc='upper right', bbox_to_anchor=(1,1), bbox_transform=ax.transAxes, title='fig.legend\nax.transAxes')
fig
Axes
The matplotlib.axes.Axes is the center of the matplotlib universe
This is a quote from Artist tutorial. This is very true because important parts of data visualization in matplotlib all come from Axes
methods.
Axes attributes |
description |
---|---|
ax.artists |
A list of Artist instances |
ax.patch |
Rectangle instance for Axes background |
ax.collections |
A list of Collection instances |
ax.images |
A list of AxesImage
|
ax.legends |
A list of Legend instances |
ax.lines |
A list of Line2D instances |
ax.patches |
A list of Patch instances |
ax.texts |
A list of Text instances |
ax.xaxis |
matplotlib.axis.XAxis instance |
ax.yaxis |
matplotlib.axis.YAxis instance |
Frequently-used commands such as ax.plot
and ax.scatter
are called "helper methods" which add corresponding Artist
s in appropriate containers and do other miscellaneous jobs.
Helper methods | Artist |
Container |
---|---|---|
ax.annotate |
Annotate |
ax.texts |
ax.bar |
Rectangle |
ax.patches |
ax.errorbar |
Line2D & Rectangle
|
ax.lines & ax.patches
|
ax.fill |
Polygon |
ax.patches |
ax.hist |
Rectangle |
ax.patches |
ax.imshow |
AxesImage |
ax.images |
ax.legend |
Legend |
ax.legends |
ax.plot |
Line2D |
ax.lines |
ax.scatter |
PathCollection |
ax.collections |
ax.text |
Text |
ax.texts |
This example shows ax.plot
and ax.scatter
add Line2D
and PathCollection
objects in corresponding lists.
x = np.linspace(0, 2*np.pi, 100)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
print('ax.lines before plot:\n', ax.lines) # empty Axes.lines
line1, = ax.plot(x, np.sin(x), label='1st plot') # add Line2D in Axes.lines
print('ax.lines after 1st plot:\n', ax.lines)
line2, = ax.plot(x, np.sin(x+np.pi/8), label='2nd plot') # add another Line2D
print('ax.lines after 2nd plot:\n', ax.lines)
print('ax.collections before scatter:\n', ax.collections)
scat = ax.scatter(x, np.random.rand(len(x)), label='scatter') # add PathCollection in Axes.collections
print('ax.collections after scatter:\n', ax.collections)
ax.legend()
print('line1:', line1)
print('line2:', line2)
print('scat:', scat)
ax.set_xlabel('x value')
ax.set_ylabel('y value')
ax.lines before plot:
[]
ax.lines after 1st plot:
[<matplotlib.lines.Line2D object at 0x1181d16d8>]
ax.lines after 2nd plot:
[<matplotlib.lines.Line2D object at 0x1181d16d8>, <matplotlib.lines.Line2D object at 0x1181d1e10>]
ax.collections before scatter:
[]
ax.collections after scatter:
[<matplotlib.collections.PathCollection object at 0x1181d74a8>]
line1: Line2D(1st plot)
line2: Line2D(2nd plot)
scat: <matplotlib.collections.PathCollection object at 0x1181d74a8>
Reusing a plotted object is not recommended
After knowing that plotted objects are contained in lists, you probably come up with an idea to reuse those objects in an Axes.lines
by appending it to another Axes.lines
list for faster plotting. Artist tutorial clearly states this is not recommended because helper methods do many things other than creating an Artist
. A quick test tells this is not a good idea.
x = np.linspace(0, 2*np.pi, 100)
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1) # upper subplot
line, = ax1.plot(x, np.sin(x), label='ax1 line') # create a Line2D object
ax1.legend()
ax2 = fig.add_subplot(2,1,2) # lower subplot
ax2.lines.append(line) # try to reuse same `Line2D` object in another `Axes`
Even add_line
method doesn't work.
ax2.add_line(line)
ValueError: Can not reset the axes. You are probably trying to re-use an artist in more than one Axes which is not supported
This error message indicates that an Artist
, container or primitive, cannot be contained in multiple containers, which is consistent with the fact that each Artist
holds the parent container as a bare object, not in a list.
print('fig:', id(fig))
print('ax1:', id(ax1))
print('line.fig:', id(line.figure))
print('line.axes:', id(line.axes))
fig: 4707121584
ax1: 4707121136
line.fig: 4707121584
line.axes: 4707121136
It might be possible if you do all the necessary stuff in a proper way, but this is far from the first idea to just append an object to a list and is enough reason not to do it.
Axis
While Axis
, appeared as XAxis
or YAxis
, only contains Artist
s related to ticks and labels, it often requires some googling for minor tweaking, occasionally for an hour. I hope this section helps you get the job done quick.
As Artist tutorial doesn't have a table like other containers, I made a similar table.
Axis attributes |
description |
---|---|
Axis.label |
A Text instance for axis label |
Axis.majorTicks |
A list of Tick instances for major ticks. |
Axis.minorTicks |
A list of Tick instances for minor ticks. |
We used ax.set_xlabel
and ax.set_ylabel
in the example for Axes
container. You might think these methods change X and Y label of Axes
instance (ax
), but in fact they change label
attributes of XAxis
and YAxis
, ax.xaxis.label
and ax.yaxis.label
, respectively.
xax = ax.xaxis
print('xax.label:', xax.label)
print('xax.majorTicks:\n', xax.majorTicks) # seven major ticks (from 0 to 6) and two invisible ticks locating outside of the figure
print('xax.minorTicks:\n', xax.minorTicks) # two ticks outside the figure
xax.label: Text(0.5,17.2,'x value')
xax.majorTicks:
[<matplotlib.axis.XTick object at 0x117ae4400>, <matplotlib.axis.XTick object at 0x117941128>, <matplotlib.axis.XTick object at 0x11732c940>, <matplotlib.axis.XTick object at 0x1177d0470>, <matplotlib.axis.XTick object at 0x1177d0390>, <matplotlib.axis.XTick object at 0x1175058d0>, <matplotlib.axis.XTick object at 0x1175050b8>, <matplotlib.axis.XTick object at 0x117bf65c0>, <matplotlib.axis.XTick object at 0x117bf6b00>]
xax.minorTicks:
[<matplotlib.axis.XTick object at 0x117ab5940>, <matplotlib.axis.XTick object at 0x117b540f0>]
ax.set_***
methods are ad hoc
Axes
has many "set_***" helper methods to modify attributes and values of Axis
and Tick
instances. They are so handy that a large portion of problems matplotlib beginners encounter can be solved with some of them. It's worth noting that these "set_***" methods are static. Changes made with them are not updated when something changed. For example, if you change X ticks with ax.set_xticks
to make them look nice with the first plot and the second plot exceeds the X range of the first plot, the result doesn't look like what it should be.
x = np.linspace(0, 2*np.pi, 100)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
line1, = ax.plot(x, np.sin(x), label='') # X range: 0 to 2pi
ax.set_xticks([0, 0.5*np.pi, np.pi, 1.5*np.pi, 2*np.pi])
line2, = ax.plot(1.5*x, np.sin(x), label='') # X range: 0 to 3pi
Ticker
does it for you
If you don't change tick-related settings with "set_***" methods, ticks and tick labels are automatically updated for each new plot accordingly. This is done by Ticker
, more specifically, formatter and locator. Although they are quite essential for tick-related settings, you might know little about them if you have solved your problems by copying and pasting Stack Overflow answers6. Let's see what's going on in the previous example.
xax = ax.xaxis
yax = ax.yaxis
print('xax.get_major_formatter()', xax.get_major_formatter())
print('yax.get_major_formatter()', yax.get_major_formatter())
print('xax.get_major_locator():', xax.get_major_locator())
print('yax.get_major_locator():', yax.get_major_locator())
xax.get_major_formatter() <matplotlib.ticker.ScalarFormatter object at 0x118af4d68>
yax.get_major_formatter() <matplotlib.ticker.ScalarFormatter object at 0x118862be0>
xax.get_major_locator(): <matplotlib.ticker.FixedLocator object at 0x1188d5908>
yax.get_major_locator(): <matplotlib.ticker.AutoLocator object at 0x118aed1d0>
ScalarFormatter
is set for both X and Y axes, because it is a default formatter and we didn't change it. On the other hand, while default AutoLocator
is set for Y axis, FixedLocator
is set for X axis which we changed tick positions by using ax.set_xticks
method. As you can imagine from its name, FixedLocator
fixes tick positions and does not update them even if the plotting range is changed.
Let's change Ticker
in the previous example instead of ax.set_xticks
.
import matplotlib.ticker as ticker # this is required to used `Ticker`
ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5*np.pi)) # locate ticks at every 0.5*pi
fig # display the figure again with new locator.
How about formatter?
@ticker.FuncFormatter # FuncFormatter can be used as a decorator
def major_formatter_radian(x, pos):
return '{}$\pi$'.format(x/np.pi) # probably not the best way to show radian tick labels
ax.xaxis.set_major_formatter(major_formatter_radian)
fig
Well, there might still be something you would like to tweak, but I guess it's clear enough.
You can learn more in matplotlib gallery.
Gallery > Tick formatters
Gallery > Tick locators
xunits
keyword for ax.plot
For your information, ax.plot
has xunits
keyword which is not described in the doc for the moment. I have never tried to use this option, but you can see an example in Gallery > Radian ticks and learn more about matplotlib.units.ConversionInterface
here.
import numpy as np
from basic_units import radians, degrees, cos
from matplotlib.pyplot import figure, show
x = [val*radians for val in np.arange(0, 15, 0.01)]
fig = figure()
fig.subplots_adjust(hspace=0.3)
ax = fig.add_subplot(211)
line1, = ax.plot(x, cos(x), xunits=radians)
ax = fig.add_subplot(212)
line2, = ax.plot(x, cos(x), xunits=degrees)
Tick
Finally, we reached at the bottom of the matplotlib hierarchy. Tick
is a small container mainly for a short line for a tick itself and a text for a tick label.
Tick attributes |
description |
---|---|
Tick.tick1line |
Line2D instance |
Tick.tick2line |
Line2D instance |
Tick.gridline |
Line2D instance for grid |
Tick.label1 |
Text instance |
Tick.label2 |
Text instance |
Tick.gridOn |
boolean which determines whether to draw the gridline |
Tick.tick1On |
boolean which determines whether to draw the 1st tickline |
Tick.tick2On |
boolean which determines whether to draw the 2nd tickline |
Tick.label1On |
boolean which determines whether to draw the 1st tick label |
Tick.label2On |
boolean which determines whether to draw the 2nd tick label |
As we see in Axis
, Tick
also appears as XTick
or YTick
. 1st and 2nd indicate ticks at lower and upper sides for XTick
, and those at left and right sides for YTick
. The latter ticks are not visible by default.
xmajortick = ax.xaxis.get_major_ticks()[2] # tick at 0.5 pi in the previous figure
print('xmajortick', xmajortick)
print('xmajortick.tick1line', xmajortick.tick1line)
print('xmajortick.tick2line', xmajortick.tick2line)
print('xmajortick.gridline', xmajortick.gridline)
print('xmajortick.label1', xmajortick.label1)
print('xmajortick.label2', xmajortick.label2)
print('xmajortick.gridOn', xmajortick.gridOn)
print('xmajortick.tick1On', xmajortick.tick1On)
print('xmajortick.tick2On', xmajortick.tick2On)
print('xmajortick.label1On', xmajortick.label1On)
print('xmajortick.label2On', xmajortick.label2On)
xmajortick <matplotlib.axis.XTick object at 0x11eec0710>
xmajortick.tick1line Line2D((1.5708,0))
xmajortick.tick2line Line2D()
xmajortick.gridline Line2D((0,0),(0,1))
xmajortick.label1 Text(1.5708,0,'0.5$\\pi$')
xmajortick.label2 Text(0,1,'0.5$\\pi$')
xmajortick.gridOn False
xmajortick.tick1On True
xmajortick.tick2On False
xmajortick.label1On True
xmajortick.label2On False
We hardly need to handle Tick
directly thanks to many helper methods, Ticker
, and Axes.tick_params
.
It's time to custom your default style
Take a look at a list of parameters for the default style.
Tutorials > Customizing matplotlib > A sample matplotlibrc file
I guess now you can figure out not only what a parameter is for, but also on which Artist
a parameter have an effect actually, which lets you save time for googling7. You can also customize the default style without making matplotlibrc file by just typing like this at the beginning of the code.
plt.rcParams['lines.linewidth'] = 2
Go and see docs (again)
Some of you might have negative impression on matplotlib's doc. I agree that it was hard to find an appropriate example for your problem from a long list. But it has been improved significantly since version 2.1.08. This is obvious if you compare corresponding pages before and after the improvement.
2.1.0 (Oct 2017) | 2.0.2 (May 2017) |
---|---|
Gallery, Tutorials | Matplotlib Examples, Thumbnail gallery |
Overview | Overview |
I recommend to take a look at the latest gallery and the tutorial which are now pretty neat.
Thanks for reading. Enjoy plotting with matplotlib (and googling) 📈🤗📊
Cover Photo by Caleb Salomons on Unsplash
-
Yes, tutorials are always informative and helpful if you are not lazy enough to read them before using. Actually, I might have tried to read a doc about Artist once when I started to plot with matplotlib a few years ago, but I'm pretty sure that I thought "OK, this is not for me" at that time. (Maybe it was not the current tutorial.) ↩
-
Here is a sample code for this figure https://matplotlib.org/gallery/showcase/anatomy.html ↩
-
Of course there are other
Artist
s. This page is a good entry for those who want a big picture. You can clickArtist
names for further explanation. ↩ -
Technically speaking,
Artist
s draw your beautiful data on a canvas in matplotlib. What a lovely rhetoric. ↩ -
fig.legend
had been not as useful as the current version because it required legend handles and labels according to the doc for ver. 2.0.2 ↩ -
You'll often come across recipes using them when you google tick-related settings one step further from "set_***" methods and give up arranging them for your own problem. (Yes, that's me several months ago.) ↩
-
Or you can dig deeper using the saved time, like I do. ↩
-
Here is a good read to know how difficult to improve the document. Matplotlib Lead Dev on Why He Can't Fix the Docs | NumFOCUS ↩
Top comments (2)
Thanks for the article. I feel matplotlib gives indeed quite some control and after a while you can start to personalize a lot of things. However, I still find applying some syle from one plot to the other is often complicated (e.g. adding a source where you want regardless of fig size, if you want to put label next to line in charts, there are no easy way to avoid overlaps, ...).
How do you deal with that? What would be needed to make the experience easier?
Thanks so much, clearing a lot for me.