text: "Artist" in Matplotlib - something I wanted to know before spending tremendous hours on googling how-tos.
  skotaro
  Updated on
  ・18 min read
  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,
  Artists. 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()
  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
  Artists 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.
  Figureknows
  Axesbut not
  Axis.
  Axesknows
  Figureand
  Axisboth.
  Axisknows
  Axesand
  Figureboth.
  Figurecan contain multiple
  Axesbecause
  fig.axesis a list of
  Axes.
  Axescan belong to only single
  Figurebecause
  ax.figureis not a list.
  Axescan have one
  XAxisand
  YAxisrespectively for similar reason.
  XAxisand
  YAxiscan belong to single
  Axesand, accordingly, single
  Figure.
  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
  Attributes with a plural name are lists and those with a singular name represent a single object. It is worth noting that
  Artists 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
  Artists 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 # 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.
  Frequently-used commands such as
  ax.plot and
  ax.scatter are called "helper methods" which add corresponding
  Artists in appropriate containers and do other miscellaneous jobs.
  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
  Artists 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.
  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.
  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.
  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 ↩
  Of course there are other
  Artists. This page is a good entry for those who want a big picture. You can click
  Artistnames for further explanation. ↩
  Technically speaking,
  Artists draw your beautiful data on a canvas in matplotlib. What a lovely rhetoric. ↩
  fig.legendhad 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 ↩
  What are your five most used terminal commands?
  community post to share our most used terminal commands.
  