At the beginning of this chapter, we briefly sketched the flow of data from user creation to its display in a user interface. Having toured matplotlib's architecture, which included taking a side trip to the namespaces and dependency graphs, there is enough context to appreciate the flow of data through the code.
As we trace through our simple line example, remember that we used the pyplot
interface. There are several other ways by which one may use matplotlib. For each of these ways, the code execution flow will be slightly different.
As a refresher, here's our code from simple-line.py
:
#! /usr/bin/env python3.4 import matplotlib.pyplot as plt def main () -> None: plt.plot([1,2,3,4]) plt.ylabel('some numbers') plt.savefig('simple-line.png') if __name__ == '__main__': main()
At the script level, here's what we've got:
matplotlib
is imported.main()
function is defined.main()
function.Having reviewed familiar territory, let's jump into what matplotlib does in the script. Here's a brief summary of the trace:
import matplotlib.pyplot
command line:pylab_setup
from matplotlib.backends
.pylab_setup
function:show
instance that you want to use, which can be integrated with the selected backend's mainloop
function.plot
function.plot
function clears the axes and creates some lines based on the provided data.canvas.draw()
function._text
attribute of the label object on the y axis..png
image. This writes the file to filesystem
by using the hardcopy backend, which correlates to the extension on the filename.We can get a hands-on look at many of these via IPython either through an interactive shell in the terminal, or with this chapter's notebook in your browser. Note that if you run the following code in the notebook, you will get different results since a different backend is being used.
The following command in the terminal will ensure that you get an interactive IPython prompt, which has access to all the dependencies:
$ make repl
Let's examine some of the things that we covered in the execution flow outline in the preceding section. We'll start by importing pyplot
and looking at the top-level setup that pyplot
initiates after the import:
In [1]: import matplotlib.pyplot as plt In [2]: plt.rcParams['backend'] Out[2]: 'MacOSX'
In some of the following calls, we will be able to access objects, methods, and so on that have been named according to the private Python naming convention. We will do this simply to explore some of the undocumented depths of matplotlib. The keyword here is undocumented. The private variables are subject to change without warning. So please do not use these in any projects.
In [3]: plt._backend_mod.__name__ Out[3]: 'matplotlib.backends.backend_macosx' In [4]: plt._show Out[4]: <matplotlib.backends.backend_macosx.Show at 0x1074bc940>
If we try to get a figure or its figure manager right now, nothing will be returned since one hasn't been created yet:
In [5]: plt._pylab_helpers.Gcf Out[5]: matplotlib._pylab_helpers.Gcf In [6]: plt._pylab_helpers.Gcf.get_active()
However, we can get the default figure manager in the following way:
In [7]: plt.get_current_fig_manager() Out[7]: FigureManager object 0x106e1ea48 wrapping NSWindow 0x103e74e90
However, note that the figure manager too doesn't have a figure yet, this can be seen in the following way:
In [8]: plt.get_current_fig_manager().figure ----------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-8-a80f1a99bf26> in <module>() ----> 1 plt.get_current_fig_manager().figure AttributeError: 'FigureManagerMac' object has no attribute 'figure'
Now, let's call the plot
function and see what's available:
In [9]: plt.plot([1,2,3,4]) Out[9]: [<matplotlib.lines.Line2D at 0x1088367b8>] In [10]: plt._pylab_helpers.Gcf.get_active() Out[10]: FigureManager object 0x1074c4a88 wrapping NSWindow 0x107026030 In [11]: plt._pylab_helpers.Gcf.get_active().canvas Out[11]: FigureCanvas object 0x1074c45c8 wrapping NSView 0x10761cd60 In [12]: plt._pylab_helpers.Gcf.get_active().canvas.figure Out[12]: <matplotlib.figure.Figure at 0x1074b5898>
Depending upon the operating system and backend that you are currently using, you may get results (or no results) that are different from the ones in the preceding section.
Better yet, by using the API function and its attributes:
In [13]: plt.get_current_fig_manager() Out[13]: FigureManager object 0x1074c4a88 wrapping NSWindow 0x107026030 In [14]: plt.get_current_fig_manager().canvas Out[14]: FigureCanvas object 0x1074c45c8 wrapping NSView 0x10761cd60 In [15]: plt.get_current_fig_manager().canvas.figure Out[15]: <matplotlib.figure.Figure at 0x1074b5898> In [16]: plt.get_current_fig_manager().canvas.figure.axes Out[16]: [<matplotlib.axes._subplots.AxesSubplot at 0x108826160>] In [17]: plt.get_current_fig_manager().canvas.figure.axes[0].lines Out[17]: [<matplotlib.lines.Line2D at 0x1088367b8>]
However, the most consistent results will be obtained when we use the pyplot
utility functions in the following way:
In [18]: plt.gcf() Out[18]: <matplotlib.figure.Figure at 0x1074b5898> In [19]: plt.gca() Out[19]: <matplotlib.axes._subplots.AxesSubplot at 0x108826160> In [20]: plt.gca().lines Out[20]: [<matplotlib.lines.Line2D at 0x1088367b8>]
The next step is to add a label in the following way:
In [21]: plt.gca().get_ylabel() Out[21]: '' In [22]: plt.ylabel('some numbers') Out[22]: <matplotlib.text.Text at 0x1088464a8> In [23]: plt.gca().get_ylabel() Out[23]: 'some numbers'
Finally, we will save the image in the following way:
In [24]: ls -al *.png ls: *.png: No such file or directory In [25]: plt.savefig('simple-line.png') In [26]: ls -al *.png -rw-r--r-- 1 oubiwann staff 22473 Nov 9 15:49 simple-line.png
A note on tracing. What we did in the previous section is a bit like sightseeing—a quick overview, some interesting moments, and then we move on to the next thing. When you really want to dive deep into the execution flow of a program, script, or a function, you perform the operation of tracing. As you might expect, the Python standard library has a module for this as well—the trace
module.
It's beyond the scope of this chapter to trace this script, but this is an excellent exercise for the motivated reader. Here is an example that illustrates the trace module's usage:
In [46]: def plotit(): plt.plot([1,2,3,4]) plt.ylabel('some numbers') plt.show() tracer = trace.Trace(countfuncs=1, countcallers=1) _ = tracer.runfunc(plotit)
This will take some time to run. When runfunc()
completes, the tracing results will be stored in tracer.results
, an instance of trace.CoverageResults
:
In [47]: results = tracer.results() _ = results.write_results(show_missing=True, summary=True, coverdir=".")
Note that by enabling countcallers
, our results will have the call relationship tracking data. With this information, you should be able to build some highly detailed graphs using NetworkX and matplotlib that visually reveal which functions in matplotlib call where and which layers of the architecture call the other layers.