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