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
1214The 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
1819The 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
2324In the future, to update the 'animated' artists we
2425
2728- show the resulting image on the screen
2829
2930thus 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
3948import matplotlib .pyplot as plt
4049import numpy as np
4150
42- x = np .linspace (0 , 2 * np .pi , 100 )
51+ x = np .linspace (0 , 2 * np .pi , 100 )
4352
4453fig , 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
5273bg = fig .canvas .copy_from_bbox (fig .bbox )
53- # draw the animated artist
74+ # draw the animated artist, this uses a cached renderer
5475ax .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
5678fig .canvas .blit (fig .bbox )
5779
5880for 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
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
86113class 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
165191fig , 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+ )
175205bm = BlitManager (fig .canvas , [ln , fr_number ])
206+ # make sure our window is on the screen and drawn
207+ plt .show (block = False )
176208plt .pause (.1 )
177209
178210for 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