Skip to content

Commit e243ecc

Browse files
tacaswellQuLogicbrunobeltran
committed
DOC: copy edit tutorial
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>
1 parent 70b87e9 commit e243ecc

File tree

2 files changed

+100
-63
lines changed

2 files changed

+100
-63
lines changed

lib/matplotlib/backend_bases.py

+1
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,7 @@ class FigureCanvasBase:
16041604

16051605
@cbook._classproperty
16061606
def supports_blit(cls):
1607+
"""If this Canvas sub-class supports blitting."""
16071608
return (hasattr(cls, "copy_from_bbox")
16081609
and hasattr(cls, "restore_region"))
16091610

tutorials/intermediate/blitting.py

+99-63
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
"""
22
=================
3-
Blitting Tutorial
3+
Blitting tutorial
44
=================
55
66
'Blitting' is a `standard technique
7-
<https://en.wikipedia.org/wiki/Bit_blit>`__ in computer graphics that
8-
in the context of matplotlib can be used to (drastically) improve
9-
performance of interactive figures. It is used internally by the
10-
:mod:`~.animation` and :mod:`~.widgets` modules for this reason.
7+
<https://en.wikipedia.org/wiki/Bit_blit>`__ in raster graphics that,
8+
in the context of Matplotlib, can be used to (drastically) improve
9+
performance of interactive figures. For example, the
10+
:mod:`~.animation` and :mod:`~.widgets` modules use blitting
11+
internally. Here, we demonstrate how to implement your own blitting, outside
12+
of these classes.
1113
1214
The source of the performance gains is simply not re-doing work we do
13-
not have to. For example, if the limits of an Axes have not changed,
14-
then there is no reason we should re-draw all of the ticks and
15-
tick-labels (particularly because text is one of the more expensive
16-
things to render).
15+
not have to. If the limits of an Axes have not changed, then there is
16+
no need to re-draw all of the ticks and tick-labels (particularly
17+
because text is one of the more expensive things to render).
1718
1819
The procedure to save our work is roughly:
1920
20-
- draw the figure, but exclude an artists marked as 'animated'
21-
- save a copy of the Agg RBGA buffer
21+
- draw the figure, but exclude any artists marked as 'animated'
22+
- save a copy of the RBGA buffer
2223
2324
In the future, to update the 'animated' artists we
2425
@@ -27,50 +28,76 @@
2728
- show the resulting image on the screen
2829
2930
thus saving us from having to re-draw everything which is _not_
30-
animated.
31+
animated. One consequence of this procedure is that your animated
32+
artists are always drawn at a higher z-order than the static artists.
3133
32-
Simple Example
33-
--------------
34+
Not all backends support blitting. You can check if a given canvas does via
35+
the `.FigureCanvasBase.supports_blit` property.
36+
37+
Minimal example
38+
---------------
39+
40+
We can use the `.FigureCanvasAgg` methods
41+
`~.FigureCanvasAgg.copy_from_bbox` and
42+
`~.FigureCanvasAgg.restore_region` in conjunction with setting
43+
``animated=True`` on our artist to implement a minimal example that
44+
uses blitting to accelerate rendering
3445
35-
We can implement this via methods on `.CanvasAgg` and setting
36-
``animated=True`` on our artist.
3746
"""
3847

3948
import matplotlib.pyplot as plt
4049
import numpy as np
4150

42-
x = np.linspace(0, 2*np.pi, 100)
51+
x = np.linspace(0, 2 * np.pi, 100)
4352

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

48-
# stop to admire our empty window axes and ensure it is drawn
49-
plt.pause(.1)
55+
# animated=True tells matplotlib to only draw the artist when we
56+
# explicitly request it
57+
(ln,) = ax.plot(x, np.sin(x), animated=True)
58+
59+
# make sure the window is raised, but the script keeps going
60+
plt.show(block=False)
5061

51-
# save a copy of the image sans animated artist
62+
# stop to admire our empty window axes and ensure it is rendered at
63+
# least once.
64+
#
65+
# We need to fully draw the figure at its final size on the screen
66+
# before we continue on so that :
67+
# a) we have the correctly sized and drawn background to grab
68+
# b) we have a cached renderer so that ``ax.draw_artist`` works
69+
# so we spin the event loop to let the backend process any pending operations
70+
plt.pause(0.1)
71+
72+
# get copy of entire figure (everything inside fig.bbox) sans animated artist
5273
bg = fig.canvas.copy_from_bbox(fig.bbox)
53-
# draw the animated artist
74+
# draw the animated artist, this uses a cached renderer
5475
ax.draw_artist(ln)
55-
# show the result to the screen
76+
# show the result to the screen, this pushes the updated RGBA buffer from the
77+
# renderer to the GUI framework so you can see it
5678
fig.canvas.blit(fig.bbox)
5779

5880
for j in range(100):
59-
# put the un-changed background back
81+
# reset the background back in the canvas state, screen unchanged
6082
fig.canvas.restore_region(bg)
61-
# update the artist.
83+
# update the artist, neither the canvas state nor the screen have changed
6284
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
63-
# re-render the artist
85+
# re-render the artist, updating the canvas state, but not the screen
6486
ax.draw_artist(ln)
65-
# copy the result to the screen
87+
# copy the image to the GUI state, but screen might not changed yet
6688
fig.canvas.blit(fig.bbox)
67-
89+
# flush any pending GUI events, re-painting the screen if needed
90+
fig.canvas.flush_events()
91+
# you can put a pause in if you want to slow things down
92+
# plt.pause(.1)
6893

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

85112

86113
class BlitManager:
87-
88-
def __init__(self, canvas, animated_artists):
114+
def __init__(self, canvas, animated_artists=()):
89115
"""
90116
Parameters
91117
----------
92-
canvas : CanvasAgg
118+
canvas : FigureCanvasAgg
93119
The canvas to work with, this only works for sub-classes of the Agg
94-
canvas which have the `~CanvasAgg.copy_from_bbox` and
95-
`~CanvasAgg.restore_region` methods.
120+
canvas which have the `~FigureCanvasAgg.copy_from_bbox` and
121+
`~FigureCanvasAgg.restore_region` methods.
96122
97-
animated_artists : Optional[List[Artist]]
123+
animated_artists : Iterable[Artist]
98124
List of the artists to manage
99125
"""
100126
self.canvas = canvas
@@ -104,11 +130,10 @@ def __init__(self, canvas, animated_artists):
104130
for a in animated_artists:
105131
self.add_artist(a)
106132
# grab the background on every draw
107-
self.cid = canvas.mpl_connect('draw_event', self.on_draw)
133+
self.cid = canvas.mpl_connect("draw_event", self.on_draw)
108134

109135
def on_draw(self, event):
110-
"""Callback to register with 'draw_event'
111-
"""
136+
"""Callback to register with 'draw_event'."""
112137
cv = self.canvas
113138
if event is not None:
114139
if event.canvas != cv:
@@ -117,67 +142,78 @@ def on_draw(self, event):
117142
self._draw_animated()
118143

119144
def add_artist(self, art):
120-
"""Add a artist to be managed
145+
"""
146+
Add an artist to be managed.
121147
122148
Parameters
123149
----------
124150
art : Artist
125-
The artist to be added. Will be set to 'animated' (just to be safe).
126-
*art* must be in the figure associated with the canvas this class
127-
is managing.
151+
152+
The artist to be added. Will be set to 'animated' (just
153+
to be safe). *art* must be in the figure associated with
154+
the canvas this class is managing.
155+
128156
"""
129157
if art.figure != self.canvas.figure:
130158
raise RuntimeError
131159
art.set_animated(True)
132160
self._artists.append(art)
133161

134162
def _draw_animated(self):
135-
"""Draw all of the animated artists
136-
"""
163+
"""Draw all of the animated artists."""
137164
fig = self.canvas.figure
138165
for a in self._artists:
139166
fig.draw_artist(a)
140167

141168
def update(self):
142-
"""Update the screen with animated artists
143-
"""
169+
"""Update the screen with animated artists."""
144170
cv = self.canvas
145171
fig = cv.figure
146172
# paranoia in case we missed the draw event,
147173
if self._bg is None:
148174
self.on_draw(None)
149175
else:
150-
# restore the old background
176+
# restore the background
151177
cv.restore_region(self._bg)
152178
# draw all of the animated artists
153179
self._draw_animated()
154-
# update the screen
180+
# update the GUI state
155181
cv.blit(fig.bbox)
156182
# let the GUI event loop process anything it has to do
157183
cv.flush_events()
158184

159185

160186
###############################################################################
161-
# And now use our class. This is a slightly more complicated example of the
162-
# first case as we add a text frame counter as well.
187+
# Here is how we would use our class. This is a slightly more complicated
188+
# example than the first case as we add a text frame counter as well.
163189

164190
# make a new figure
165191
fig, ax = plt.subplots()
166192
# add a line
167-
ln, = ax.plot(x, np.sin(x), animated=True)
193+
(ln,) = ax.plot(x, np.sin(x), animated=True)
168194
# add a frame number
169-
fr_number = ax.annotate('0', (0, 1),
170-
xycoords='axes fraction',
171-
xytext=(10, -10),
172-
textcoords='offset points',
173-
ha='left', va='top',
174-
animated=True)
195+
fr_number = ax.annotate(
196+
"0",
197+
(0, 1),
198+
xycoords="axes fraction",
199+
xytext=(10, -10),
200+
textcoords="offset points",
201+
ha="left",
202+
va="top",
203+
animated=True,
204+
)
175205
bm = BlitManager(fig.canvas, [ln, fr_number])
206+
# make sure our window is on the screen and drawn
207+
plt.show(block=False)
176208
plt.pause(.1)
177209

178210
for j in range(100):
179211
# update the artists
180212
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
181-
fr_number.set_text('frame: {j}'.format(j=j))
213+
fr_number.set_text("frame: {j}".format(j=j))
182214
# tell the blitting manager to do it's thing
183215
bm.update()
216+
217+
###############################################################################
218+
# This class does not depend on `.pyplot` and is suitable to embed
219+
# into larger GUI application.

0 commit comments

Comments
 (0)