8000 DOC: copy edit tutorial · matplotlib/matplotlib@a549e10 · GitHub
[go: up one dir, main page]

Skip to content

Commit a549e10

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 00851f7 commit a549e10

File tree

2 files changed

+105
-63
lines changed

2 files changed

+105
-63
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,7 @@ class FigureCanvasBase:
16711671

16721672
@cbook._classproperty
16731673
def supports_blit(cls):
1674+
"""If this Canvas sub-class supports blitting."""
16741675
return (hasattr(cls, "copy_from_bbox")
16751676
and hasattr(cls, "restore_region"))
16761677

tutorials/intermediate/blitting.py

Lines changed: 104 additions & 63 deletions
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,81 @@
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+
.. warning::
38+
39+
This code does not work with the OSX backend (but does work with other
40+
GUI backends on mac).
41+
42+
Minimal example
43+
---------------
44+
45+
We can use the `.FigureCanvasAgg` methods
46+
`~.FigureCanvasAgg.copy_from_bbox` and
47+
`~.FigureCanvasAgg.restore_region` in conjunction with setting
48+
``animated=True`` on our artist to implement a minimal example that
49+
uses blitting to accelerate rendering
3450
35-
We can implement this via methods on `.CanvasAgg` and setting
36-
``animated=True`` on our artist.
3751
"""
3852

3953
import matplotlib.pyplot as plt
4054
import numpy as np
4155

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

4458
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)
4759

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

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

5885
for j in range(100):
59-
# put the un-changed background back
86+
# reset the background back in the canvas state, screen unchanged
6087
fig.canvas.restore_region(bg)
61-
# update the artist.
88+
# update the artist, neither the canvas state nor the screen have changed
6289
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
63-
# re-render the artist
90+
# re-render the artist, updating the canvas state, but not the screen
6491
ax.draw_artist(ln)
65-
# copy the result to the screen
92+
# copy the image to the GUI state, but screen might not changed yet
6693
fig.canvas.blit(fig.bbox)
67-
94+
# flush any pending GUI events, re-painting the screen if needed
95+
fig.canvas.flush_events()
96+
# you can put a pause in if you want to slow things down
97+
# plt.pause(.1)
6898

6999
###############################################################################
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
100+
# This example works and shows a simple animation, however because we
101+
# are only grabbing the background once, if the size of the figure in
102+
# pixels changes (due to either the size or dpi of the figure
103+
# changing) , the background will be invalid and result in incorrect
104+
# (but sometimes cool looking!) images. There is also a global
105+
# variable and a fair amount of boiler plate which suggests we should
74106
# wrap this in a class.
75107
#
76108
# Class-based example
@@ -80,21 +112,20 @@
80112
# restoring the background, drawing the artists, and then blitting the
81113
# result to the screen. Additionally, we can use the ``'draw_event'``
82114
# callback to capture a new background whenever a full re-draw
83-
# happens.
115+
# happens to handle resizes correctly.
84116

85117

86118
class BlitManager:
87-
88-
def __init__(self, canvas, animated_artists):
119+
def __init__(self, canvas, animated_artists=()):
89120
"""
90121
Parameters
91122
----------
92-
canvas : CanvasAgg
123+
canvas : FigureCanvasAgg
93124
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.
125+
canvas which have the `~FigureCanvasAgg.copy_from_bbox` and
126+
`~FigureCanvasAgg.restore_region` methods.
96127
97-
animated_artists : Optional[List[Artist]]
128+
animated_artists : Iterable[Artist]
98129
List of the artists to manage
99130
"""
100131
self.canvas = canvas
@@ -104,11 +135,10 @@ def __init__(self, canvas, animated_artists):
104135
for a in animated_artists:
105136
self.add_artist(a)
106137
# grab the background on every draw
107-
self.cid = canvas.mpl_connect('draw_event', self.on_draw)
138+
self.cid = canvas.mpl_connect("draw_event", self.on_draw)
108139

109140
def on_draw(self, event):
110-
"""Callback to register with 'draw_event'
111-
"""
141+
"""Callback to register with 'draw_event'."""
112142
cv = self.canvas
113143
if event is not None:
114144
if event.canvas != cv:
@@ -117,67 +147,78 @@ def on_draw(self, event):
117147
self._draw_animated()
118148

119149
def add_artist(self, art):
120-
"""Add a artist to be managed
150+
"""
151+
Add an artist to be managed.
121152
122153
Parameters
123154
----------
124155
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.
156+
157+
The artist to be added. Will be set to 'animated' (just
158+
to be safe). *art* must be in the figure associated with
159+
the canvas this class is managing.
160+
128161
"""
129162
if art.figure != self.canvas.figure:
130163
raise RuntimeError
131164
art.set_animated(True)
132165
self._artists.append(art)
133166

134167
def _draw_animated(self):
135-
"""Draw all of the animated artists
136-
"""
168+
"""Draw all of the animated artists."""
137169
fig = self.canvas.figure
138170
for a in self._artists:
139171
fig.draw_artist(a)
140172

141173
def update(self):
142-
"""Update the screen with animated artists
143-
"""
174+
"""Update the screen with animated artists."""
144175
cv = self.canvas
145176
fig = cv.figure
146177
# paranoia in case we missed the draw event,
147178
if self._bg is None:
148179
self.on_draw(None)
149180
else:
150-
# restore the old background
181+
# restore the background
151182
cv.restore_region(self._bg)
152183
# draw all of the animated artists
153184
self._draw_animated()
154-
# update the screen
185+
# update the GUI state
155186
cv.blit(fig.bbox)
156187
# let the GUI event loop process anything it has to do
157188
cv.flush_events()
158189

159190

160191
###############################################################################
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.
192+
# Here is how we would use our class. This is a slightly more complicated
193+
# example than the first case as we add a text frame counter as well.
163194

164195
# make a new figure
165196
fig, ax = plt.subplots()
166197
# add a line
167-
ln, = ax.plot(x, np.sin(x), animated=True)
198+
(ln,) = ax.plot(x, np.sin(x), animated=True)
168199
# 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)
200+
fr_number = ax.annotate(
201+
"0",
202+
(0, 1),
203+
xycoords="axes fraction",
204+
xytext=(10, -10),
205+
textcoords="offset points",
206+
ha="left",
207+
va="top",
208+
animated=True,
209+
)
175210
bm = BlitManager(fig.canvas, [ln, fr_number])
211+
# make sure our window is on the screen and drawn
212+
plt.show(block=False)
176213
plt.pause(.1)
177214

178215
for j in range(100):
179216
# update the artists
180217
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
181-
fr_number.set_text('frame: {j}'.format(j=j))
218+
fr_number.set_text("frame: {j}".format(j=j))
182219
# tell the blitting manager to do it's thing
183220
bm.update()
221+
222+
###############################################################################
223+
# This class does not depend on `.pyplot` and is suitable to embed
224+
# into larger GUI application.

0 commit comments

Comments
 (0)
0