Skip to content

Commit

Permalink
DOC: copy edit tutorial
Browse files Browse the repository at this point in the history
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Co-authored-by: Bruno Beltran <brunobeltran0@gmail.com>
Co-authored-by: Tim Hoffmann<2836374+timhoffm@users.noreply.github.com>
  • Loading branch information
3 people committed Jun 11, 2020
1 parent 4192f98 commit 0603a00
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 63 deletions.
1 change: 1 addition & 0 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,7 @@ class FigureCanvasBase:

@cbook._classproperty
def supports_blit(cls):
"""If this Canvas sub-class supports blitting."""
return (hasattr(cls, "copy_from_bbox")
and hasattr(cls, "restore_region"))

Expand Down
162 changes: 99 additions & 63 deletions tutorials/intermediate/blitting.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
"""
=================
Blitting Tutorial
Blitting tutorial
=================
'Blitting' is a `standard technique
<https://en.wikipedia.org/wiki/Bit_blit>`__ in computer graphics that
in the context of matplotlib can be used to (drastically) improve
performance of interactive figures. It is used internally by the
:mod:`~.animation` and :mod:`~.widgets` modules for this reason.
<https://en.wikipedia.org/wiki/Bit_blit>`__ in raster graphics that,
in the context of Matplotlib, can be used to (drastically) improve
performance of interactive figures. For example, the
:mod:`~.animation` and :mod:`~.widgets` modules use blitting
internally. Here, we demonstrate how to implement your own blitting, outside
of these classes.
The source of the performance gains is simply not re-doing work we do
not have to. For example, if the limits of an Axes have not changed,
then there is no reason we should re-draw all of the ticks and
tick-labels (particularly because text is one of the more expensive
things to render).
not have to. If the limits of an Axes have not changed, then there is
no need to re-draw all of the ticks and tick-labels (particularly
because text is one of the more expensive things to render).
The procedure to save our work is roughly:
- draw the figure, but exclude an artists marked as 'animated'
- save a copy of the Agg RBGA buffer
- draw the figure, but exclude any artists marked as 'animated'
- save a copy of the RBGA buffer
In the future, to update the 'animated' artists we
Expand All @@ -27,50 +28,76 @@
- show the resulting image on the screen
thus saving us from having to re-draw everything which is _not_
animated.
animated. One consequence of this procedure is that your animated
artists are always drawn at a higher z-order than the static artists.
Simple Example
--------------
Not all backends support blitting. You can check if a given canvas does via
the `.FigureCanvasBase.supports_blit` property.
Minimal example
---------------
We can use the `.FigureCanvasAgg` methods
`~.FigureCanvasAgg.copy_from_bbox` and
`~.FigureCanvasAgg.restore_region` in conjunction with setting
``animated=True`` on our artist to implement a minimal example that
uses blitting to accelerate rendering
We can implement this via methods on `.CanvasAgg` and setting
``animated=True`` on our artist.
"""

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2*np.pi, 100)
x = np.linspace(0, 2 * np.pi, 100)

fig, ax = plt.subplots()
# animated=True makes the artist be excluded from normal draw tree
ln, = ax.plot(x, np.sin(x), animated=True)

# stop to admire our empty window axes and ensure it is drawn
plt.pause(.1)
# animated=True tells matplotlib to only draw the artist when we
# explicitly request it
(ln,) = ax.plot(x, np.sin(x), animated=True)

# make sure the window is raised, but the script keeps going
plt.show(block=False)

# save a copy of the image sans animated artist
# stop to admire our empty window axes and ensure it is rendered at
# least once.
#
# We need to fully draw the figure at its final size on the screen
# before we continue on so that :
# a) we have the correctly sized and drawn background to grab
# b) we have a cached renderer so that ``ax.draw_artist`` works
# so we spin the event loop to let the backend process any pending operations
plt.pause(0.1)

# get copy of entire figure (everything inside fig.bbox) sans animated artist
bg = fig.canvas.copy_from_bbox(fig.bbox)
# draw the animated artist
# draw the animated artist, this uses a cached renderer
ax.draw_artist(ln)
# show the result to the screen
# show the result to the screen, this pushes the updated RGBA buffer from the
# renderer to the GUI framework so you can see it
fig.canvas.blit(fig.bbox)

for j in range(100):
# put the un-changed background back
# reset the background back in the canvas state, screen unchanged
fig.canvas.restore_region(bg)
# update the artist.
# update the artist, neither the canvas state nor the screen have changed
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
# re-render the artist
# re-render the artist, updating the canvas state, but not the screen
ax.draw_artist(ln)
# copy the result to the screen
# copy the image to the GUI state, but screen might not changed yet
fig.canvas.blit(fig.bbox)

# flush any pending GUI events, re-painting the screen if needed
fig.canvas.flush_events()
# you can put a pause in if you want to slow things down
# plt.pause(.1)

###############################################################################
# This example works and shows a simple animation, however because we are only
# grabbing the background once, if the size of dpi of the figure change, the
# background will be invalid and result in incorrect images. There is also a
# global variable and a fair amount of boiler plate which suggests we should
# This example works and shows a simple animation, however because we
# are only grabbing the background once, if the size of the figure in
# pixels changes (due to either the size or dpi of the figure
# changing) , the background will be invalid and result in incorrect
# (but sometimes cool looking!) images. There is also a global
# variable and a fair amount of boiler plate which suggests we should
# wrap this in a class.
#
# Class-based example
Expand All @@ -80,21 +107,20 @@
# restoring the background, drawing the artists, and then blitting the
# result to the screen. Additionally, we can use the ``'draw_event'``
# callback to capture a new background whenever a full re-draw
# happens.
# happens to handle resizes correctly.


class BlitManager:

def __init__(self, canvas, animated_artists):
def __init__(self, canvas, animated_artists=()):
"""
Parameters
----------
canvas : CanvasAgg
canvas : FigureCanvasAgg
The canvas to work with, this only works for sub-classes of the Agg
canvas which have the `~CanvasAgg.copy_from_bbox` and
`~CanvasAgg.restore_region` methods.
canvas which have the `~FigureCanvasAgg.copy_from_bbox` and
`~FigureCanvasAgg.restore_region` methods.
animated_artists : Optional[List[Artist]]
animated_artists : Iterable[Artist]
List of the artists to manage
"""
self.canvas = canvas
Expand All @@ -104,11 +130,10 @@ def __init__(self, canvas, animated_artists):
for a in animated_artists:
self.add_artist(a)
# grab the background on every draw
self.cid = canvas.mpl_connect('draw_event', self.on_draw)
self.cid = canvas.mpl_connect("draw_event", self.on_draw)

def on_draw(self, event):
"""Callback to register with 'draw_event'
"""
"""Callback to register with 'draw_event'."""
cv = self.canvas
if event is not None:
if event.canvas != cv:
Expand All @@ -117,67 +142,78 @@ def on_draw(self, event):
self._draw_animated()

def add_artist(self, art):
"""Add a artist to be managed
"""
Add an artist to be managed.
Parameters
----------
art : Artist
The artist to be added. Will be set to 'animated' (just to be safe).
*art* must be in the figure associated with the canvas this class
is managing.
The artist to be added. Will be set to 'animated' (just
to be safe). *art* must be in the figure associated with
the canvas this class is managing.
"""
if art.figure != self.canvas.figure:
raise RuntimeError
art.set_animated(True)
self._artists.append(art)

def _draw_animated(self):
"""Draw all of the animated artists
"""
"""Draw all of the animated artists."""
fig = self.canvas.figure
for a in self._artists:
fig.draw_artist(a)

def update(self):
"""Update the screen with animated artists
"""
"""Update the screen with animated artists."""
cv = self.canvas
fig = cv.figure
# paranoia in case we missed the draw event,
if self._bg is None:
self.on_draw(None)
else:
# restore the old background
# restore the background
cv.restore_region(self._bg)
# draw all of the animated artists
self._draw_animated()
# update the screen
# update the GUI state
cv.blit(fig.bbox)
# let the GUI event loop process anything it has to do
cv.flush_events()


###############################################################################
# And now use our class. This is a slightly more complicated example of the
# first case as we add a text frame counter as well.
# Here is how we would use our class. This is a slightly more complicated
# example than the first case as we add a text frame counter as well.

# make a new figure
fig, ax = plt.subplots()
# add a line
ln, = ax.plot(x, np.sin(x), animated=True)
(ln,) = ax.plot(x, np.sin(x), animated=True)
# add a frame number
fr_number = ax.annotate('0', (0, 1),
xycoords='axes fraction',
xytext=(10, -10),
textcoords='offset points',
ha='left', va='top',
animated=True)
fr_number = ax.annotate(
"0",
(0, 1),
xycoords="axes fraction",
xytext=(10, -10),
textcoords="offset points",
ha="left",
va="top",
animated=True,
)
bm = BlitManager(fig.canvas, [ln, fr_number])
# make sure our window is on the screen and drawn
plt.show(block=False)
plt.pause(.1)

for j in range(100):
# update the artists
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
fr_number.set_text('frame: {j}'.format(j=j))
fr_number.set_text("frame: {j}".format(j=j))
# tell the blitting manager to do it's thing
bm.update()

###############################################################################
# This class can does not depend on `.pyplot` and is suitable to embed
# into larger GUI application.

0 comments on commit 0603a00

Please sign in to comment.