"Artist" in Matplotlib - something I wanted to know before spending tremendous hours on googling how-tos.
Tags: python, matplotlib, dataviz, datascience
Published: 2018-03-13 16:50:38

---
title: "Artist" in Matplotlib - something I wanted to know before spending tremendous hours on googling how-tos.
published: true
description: Things to know before diving into Stack Overflow answers.
tags: python, matplotlib, dataviz, datascience
cover_image: https://thepracticaldev.s3.amazonaws.com/i/q83y6wcm6fuoy3sizuju.jpg 
---

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](https://matplotlib.org/tutorials/index.html) 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 tweaking[^1]. 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](https://qiita.com/skotaro/items/08dc0b8c5704c94eafb9), and is mostly based on [Artist tutorial](https://matplotlib.org/tutorials/intermediate/artists.html) and [Usage Guide](https://matplotlib.org/tutorials/introductory/usage.html) (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 

```python
%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](https://matplotlib.org/tutorials/introductory/lifecycle.html#a-note-on-the-object-oriented-api-vs-pyplot) and [Coding Styles](https://matplotlib.org/tutorials/introductory/usage.html#coding-styles), here I only make some comments on them. If you look for introduction for them, I recommend official tutorials.

 - [Tutorials > Introductory > The Lifecycle of a plot](https://matplotlib.org/tutorials/introductory/lifecycle.html)
 - [Tutorials > Introductory > Pyplot tutorial](https://matplotlib.org/tutorials/introductory/pyplot.html)

## 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.


```python
fig, ax = plt.subplots()
ax.plot(x,y)
```

```python
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.***`. 

```python
# 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()
```

![Pyplot example](https://thepracticaldev.s3.amazonaws.com/i/sv7utz8ry8yz8rr4c0sd.png)

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](https://matplotlib.org/tutorials/introductory/pyplot.html), 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](https://matplotlib.org/1.5.1/faq/usage_faq.html#parts-of-a-figure) has a nice image explaining this.

![Hierarchy in matplotlib](https://thepracticaldev.s3.amazonaws.com/i/rr39m52m6peef1drke7m.png)

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.


```python
fig, ax = plt.subplots() # make Figure and Axes which belongs to 'fig'
```

```python
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.

```python
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)
```

```:output
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` knows `Axes` but not `Axis`.
 - `Axes` knows `Figure` and `Axis` both.
 - `Axis` knows `Axes` and `Figure` both.
 - `Figure` can contain multiple `Axes` because `fig.axes` is a list of `Axes`.
 - `Axes` can belong to only single `Figure` because `ax.figure` is not a list.
 - `Axes` can have one `XAxis` and `YAxis` respectively for similar reason.
 - `XAxis` and `YAxis` can belong to single `Axes` and, accordingly, single `Figure`.

# Everything in your plot is an `Artist`
Instead of a figure explaining the hierarchical structure, [Usage Guide](https://matplotlib.org/tutorials/introductory/usage.html) in the current doc has  "Anatomy of a figure"[^2] explaining all components in a figure which is also informative[^3].


![Anatomy of a figure in matplotlib](https://thepracticaldev.s3.amazonaws.com/i/b9psb0mtz7yk8qmfe26f.png)

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` object[^4]. 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 and primitives](https://thepracticaldev.s3.amazonaws.com/i/la33f9zwg65hqjz9j4ee.png)


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.


```python
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)
```

```:output
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)
```

![Adding Line2D in ax.lines](https://thepracticaldev.s3.amazonaws.com/i/xv5hepc9gloota0kmg2b.png)


Following sections summarize four containers. Tables are copied from [Artist tutorial]( https://matplotlib.org/tutorials/intermediate/artists.html).

## `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`](https://matplotlib.org/users/transforms_tutorial.html), 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](https://matplotlib.org/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.legend). 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.


```python
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()
```
![Using ax.legend twice](https://thepracticaldev.s3.amazonaws.com/i/1kdkx0tl4tcsj36ay320.png)


A famous recipe to put them together is to combine legend handlers and labels of both `Axes`.

```python
# 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
```
![Combining two legends](https://thepracticaldev.s3.amazonaws.com/i/fiiem6k0tj6cg7sjtd20.png)


This can be easily done by `fig.legend` without arguments which was introduced in [ver 2.1](https://matplotlib.org/users/whats_new.html#figure-legend-can-be-called-without-arguments)[^5]. 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.


```python
fig.legend(loc='upper right', bbox_to_anchor=(1,1), bbox_transform=ax.transAxes, title='fig.legend\nax.transAxes')
fig
```
![fig.legend with ax.transAxes](https://thepracticaldev.s3.amazonaws.com/i/o6wguud2b5jppe9516z3.png)

## `Axes`
> The matplotlib.axes.Axes is the center of the matplotlib universe 

This is a quote from [Artist tutorial](https://matplotlib.org/tutorials/intermediate/artists.html#axes-container). 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.

```python
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')
```
```:output
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>
```

![ax,plot and ax.scatter](https://thepracticaldev.s3.amazonaws.com/i/atx3762hgn3lw90jzd9x.png)

### 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](https://matplotlib.org/tutorials/intermediate/artists.html#axes-container) 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.

```python
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`
```

![Reusing Line2D is not a good idea](https://thepracticaldev.s3.amazonaws.com/i/gdwn7kv4tnfw8xzxj8uo.png)

Even `add_line` method doesn't work.

```python
ax2.add_line(line)
```
```:output
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.


```python
print('fig:', id(fig)) 
print('ax1:', id(ax1))
print('line.fig:', id(line.figure))
print('line.axes:', id(line.axes))
```
```:output
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](https://matplotlib.org/tutorials/intermediate/artists.html#axis-containers) 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.


```python
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
```
```:output
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.

```python
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
```

![ax.set_*** methods are ad hoc](https://thepracticaldev.s3.amazonaws.com/i/opdcyovaw29bz1snbg8h.png)

### `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 answers[^6]. Let's see what's going on in the previous example.

```python
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())
```
```:output
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`.

```python
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.
```

![Locator updates tick positions automatically](https://thepracticaldev.s3.amazonaws.com/i/42ik65x17af7s5fhst0i.png)

How about formatter?

```python
@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
```

![Formatter changes tick label formatting](https://thepracticaldev.s3.amazonaws.com/i/tk3ckc24mxlllyu9n9v0.png)

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](https://matplotlib.org/gallery/ticks_and_spines/tick-formatters.html)
[Gallery > Tick locators](https://matplotlib.org/gallery/ticks_and_spines/tick-locators.html)


### `xunits` keyword for `ax.plot`
For your information, `ax.plot` has `xunits` keyword which is [not described in the doc](https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.plot.html#matplotlib.axes.Axes.plot) for the moment. I have never tried to use this option, but you can see an example in [Gallery > Radian ticks](https://matplotlib.org/gallery/units/radian_demo.html) and learn more about `matplotlib.units.ConversionInterface` [here](https://matplotlib.org/api/units_api.html).

```python
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)
```
<img width="440" alt="xunits keyword example" src="https://thepracticaldev.s3.amazonaws.com/i/9ns9nbzjy5z2glzeuhmb.png">


## `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.

```python
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)
```
```:output
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`](https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.tick_params.html).

# 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](https://matplotlib.org/tutorials/introductory/customizing.html#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 googling[^7]. You can also customize the default style without making matplotlibrc file by just typing like this at the beginning of the code.

```python
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.0[^8]. This is obvious if you compare corresponding pages before and after the improvement.

|2.1.0 (Oct 2017)|2.0.2 (May 2017)|
|:--|:--|
|[Gallery](https://matplotlib.org/2.1.0/gallery/index.html), [Tutorials](https://matplotlib.org/2.1.0/tutorials/index.html)|[Matplotlib Examples](https://matplotlib.org/2.0.2/examples/index.html), [Thumbnail gallery](https://matplotlib.org/2.0.2/gallery.html)|
|[Overview](https://matplotlib.org/2.1.0/contents.html)|[Overview](https://matplotlib.org/2.0.2/contents.html)|

I recommend to take a look at [the latest gallery](https://matplotlib.org/gallery/index.html) and [the tutorial](https://matplotlib.org/tutorials/index.html#) which are now pretty neat.

***Thanks for reading. Enjoy plotting with matplotlib (and googling)*** 📈🤗📊

---

*Cover Photo by [Caleb Salomons on Unsplash](https://unsplash.com/photos/zpeA42RVIQg)*


[^1]: 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.)
[^2]: Here is a sample code for this figure  https://matplotlib.org/gallery/showcase/anatomy.html
[^3]: Of course there are other `Artist`s. [This page](https://matplotlib.org/api/artist_api.html) is a good entry for those who want a big picture. You can click `Artist` names for further explanation.
[^4]: Technically speaking, `Artist`s draw your beautiful data on a canvas in matplotlib. What a lovely rhetoric.
[^5]: `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](https://matplotlib.org/2.0.2/api/figure_api.html?highlight=figure%20legend#matplotlib.figure.Figure.legend)
[^6]: 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.)
[^7]: Or you can dig deeper using the saved time, like I do.
[^8]: 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](https://www.numfocus.org/blog/matplotlib-lead-developer-explains-why-he-cant-fix-the-docs-but-you-can/)