1
1
"""
2
2
=================
3
- Blitting Tutorial
3
+ Blitting tutorial
4
4
=================
5
5
6
6
'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.
11
13
12
14
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).
17
18
18
19
The procedure to save our work is roughly:
19
20
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
22
23
23
24
In the future, to update the 'animated' artists we
24
25
27
28
- show the resulting image on the screen
28
29
29
30
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.
31
33
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
34
45
35
- We can implement this via methods on `.CanvasAgg` and setting
36
- ``animated=True`` on our artist.
37
46
"""
38
47
39
48
import matplotlib .pyplot as plt
40
49
import numpy as np
41
50
42
- x = np .linspace (0 , 2 * np .pi , 100 )
51
+ x = np .linspace (0 , 2 * np .pi , 100 )
43
52
44
53
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 )
47
54
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 )
50
61
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
52
73
bg = fig .canvas .copy_from_bbox (fig .bbox )
53
- # draw the animated artist
74
+ # draw the animated artist, this uses a cached renderer
54
75
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
56
78
fig .canvas .blit (fig .bbox )
57
79
58
80
for j in range (100 ):
59
- # put the un-changed background back
81
+ # reset the background back in the canvas state, screen unchanged
60
82
fig .canvas .restore_region (bg )
61
- # update the artist.
83
+ # update the artist, neither the canvas state nor the screen have changed
62
84
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
64
86
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
66
88
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)
68
93
69
94
###############################################################################
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
74
101
# wrap this in a class.
75
102
#
76
103
# Class-based example
80
107
# restoring the background, drawing the artists, and then blitting the
81
108
# result to the screen. Additionally, we can use the ``'draw_event'``
82
109
# callback to capture a new background whenever a full re-draw
83
- # happens.
110
+ # happens to handle resizes correctly .
84
111
85
112
86
113
class BlitManager :
87
-
88
- def __init__ (self , canvas , animated_artists ):
114
+ def __init__ (self , canvas , animated_artists = ()):
89
115
"""
90
116
Parameters
91
117
----------
92
- canvas : CanvasAgg
118
+ canvas : FigureCanvasAgg
93
119
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.
96
122
97
- animated_artists : Optional[List[ Artist] ]
123
+ animated_artists : Iterable[ Artist]
98
124
List of the artists to manage
99
125
"""
100
126
self .canvas = canvas
@@ -104,11 +130,10 @@ def __init__(self, canvas, animated_artists):
104
130
for a in animated_artists :
105
131
self .add_artist (a )
106
132
# 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 )
108
134
109
135
def on_draw (self , event ):
110
- """Callback to register with 'draw_event'
111
- """
136
+ """Callback to register with 'draw_event'."""
112
137
cv = self .canvas
113
138
if event is not None :
114
139
if event .canvas != cv :
@@ -117,67 +142,78 @@ def on_draw(self, event):
117
142
self ._draw_animated ()
118
143
119
144
def add_artist (self , art ):
120
- """Add a artist to be managed
145
+ """
146
+ Add an artist to be managed.
121
147
122
148
Parameters
123
149
----------
124
150
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
+
128
156
"""
129
157
if art .figure != self .canvas .figure :
130
158
raise RuntimeError
131
159
art .set_animated (True )
132
160
self ._artists .append (art )
133
161
134
162
def _draw_animated (self ):
135
- """Draw all of the animated artists
136
- """
163
+ """Draw all of the animated artists."""
137
164
fig = self .canvas .figure
138
165
for a in self ._artists :
139
166
fig .draw_artist (a )
140
167
141
168
def update (self ):
142
- """Update the screen with animated artists
143
- """
169
+ """Update the screen with animated artists."""
144
170
cv = self .canvas
145
171
fig = cv .figure
146
172
# paranoia in case we missed the draw event,
147
173
if self ._bg is None :
148
174
self .on_draw (None )
149
175
else :
150
- # restore the old background
176
+ # restore the background
151
177
cv .restore_region (self ._bg )
152
178
# draw all of the animated artists
153
179
self ._draw_animated ()
154
- # update the screen
180
+ # update the GUI state
155
181
cv .blit (fig .bbox )
156
182
# let the GUI event loop process anything it has to do
157
183
cv .flush_events ()
158
184
159
185
160
186
###############################################################################
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.
163
189
164
190
# make a new figure
165
191
fig , ax = plt .subplots ()
166
192
# add a line
167
- ln , = ax .plot (x , np .sin (x ), animated = True )
193
+ ( ln ,) = ax .plot (x , np .sin (x ), animated = True )
168
194
# 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
+ )
175
205
bm = BlitManager (fig .canvas , [ln , fr_number ])
206
+ # make sure our window is on the screen and drawn
207
+ plt .show (block = False )
176
208
plt .pause (.1 )
177
209
178
210
for j in range (100 ):
179
211
# update the artists
180
212
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 ))
182
214
# tell the blitting manager to do it's thing
183
215
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