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+ .. 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
3953import matplotlib .pyplot as plt
4054import numpy as np
4155
42- x = np .linspace (0 , 2 * np .pi , 100 )
56+ x = np .linspace (0 , 2 * np .pi , 100 )
4357
4458fig , 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
5278bg = fig .canvas .copy_from_bbox (fig .bbox )
53- # draw the animated artist
79+ # draw the animated artist, this uses a cached renderer
5480ax .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
5683fig .canvas .blit (fig .bbox )
5784
5885for 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
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
86118class 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
165196fig , 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+ )
175210bm = BlitManager (fig .canvas , [ln , fr_number ])
211+ # make sure our window is on the screen and drawn
212+ plt .show (block = False )
176213plt .pause (.1 )
177214
178215for 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