@@ -181,7 +181,7 @@ def _plot_array(array, *args, x=None, y=None, series=None, _x_axes_last=False, *
181181 @deprecate_kwarg ('stacked' , 'stack' )
182182 def __call__ (self , x = None , y = None , ax = None , subplots = False , layout = None , figsize = None ,
183183 sharex = None , sharey = False , tight_layout = None , constrained_layout = None , title = None , legend = None ,
184- animate = None , filepath = None , ** kwargs ):
184+ animate = None , filepath = None , show = None , ** kwargs ):
185185 from matplotlib import pyplot as plt
186186
187187 array = self .array
@@ -200,14 +200,14 @@ def __call__(self, x=None, y=None, ax=None, subplots=False, layout=None, figsize
200200 kwargs ['stacked' ] = True
201201
202202 animate_axes , subplot_axes , x , y , series_axes = PlotObject ._handle_x_y_axes (array .axes , animate , subplots , x , y )
203-
203+ if show is None :
204+ show = filepath is None and ax is None
204205 if constrained_layout is None and tight_layout is None :
205206 constrained_layout = True
206207
207208 if ax is None :
208209 fig = plt .figure (figsize = figsize , tight_layout = tight_layout , constrained_layout = constrained_layout )
209-
210- if subplots :
210+ if subplots or layout is not None :
211211 if layout is None :
212212 subplots_shape = subplot_axes .shape
213213 if len (subplots_shape ) > 2 :
@@ -217,37 +217,33 @@ def __call__(self, x=None, y=None, ax=None, subplots=False, layout=None, figsize
217217 layout = subplot_axes .shape
218218 if sharex is None :
219219 sharex = True
220- ax = fig .subplots (* layout , sharex = sharex , sharey = sharey )
220+ ax_to_return = fig .subplots (* layout , sharex = sharex , sharey = sharey )
221+ ax = ax_to_return if subplots else ax_to_return .flat [0 ]
221222 else :
222223 ax = fig .add_subplot ()
224+ ax_to_return = ax
225+ else :
226+ fig = ax .figure
227+ ax_to_return = ax
223228
229+ anim_kwargs = kwargs .pop ('anim_params' , {})
224230 if animate :
225- import matplotlib .animation as animation
231+ from matplotlib .animation import FuncAnimation
226232
227- def run ( t ) :
228- if subplots :
233+ if subplots :
234+ def run ( t ) :
229235 for subplot_ax in ax .flat :
230236 subplot_ax .clear ()
231- else :
237+ self ._plot_many (array [t ], ax , kwargs , series_axes , subplot_axes , title , x , y )
238+ else :
239+ def run (t ):
232240 ax .clear ()
233- self ._plot_many (array [t ], ax , kwargs , series_axes , subplot_axes , title , x , y )
241+ self ._plot_many (array [t ], ax , kwargs , series_axes , subplot_axes , title , x , y )
234242 # TODO: add support for interpolation between frames/labels
235243 # see https://github.com/julkaar9/pynimate for inspiration
236- ani = animation .FuncAnimation (fig , run , frames = animate_axes .iter_labels ())
237- if not isinstance (filepath , Path ):
238- filepath = Path (filepath )
239- print (f"Writing animation to { filepath } ..." , end = ' ' , flush = True )
240- if '.htm' in filepath .suffix :
241- filepath .write_text (f'<html>{ ani .to_html5_video ()} </html>' , encoding = 'utf8' )
242- else :
243- # writer = self.writer
244- # if writer is None:
245- writer = 'pillow' if filepath .suffix == '.gif' else 'ffmpeg'
246- fps = 5
247- metadata = None
248- bitrate = None
249- ani .save (filepath , writer = writer , fps = fps , metadata = metadata , bitrate = bitrate )
244+ ani = FuncAnimation (fig , run , frames = animate_axes .iter_labels ())
250245 else :
246+ ani = None
251247 self ._plot_many (array , ax , kwargs , series_axes , subplot_axes , title , x , y )
252248
253249 if legend or legend is None :
@@ -267,7 +263,56 @@ def run(t):
267263 # use figure to place legend to add a single legend for all subplots
268264 legend_parent = first_ax .figure if subplots else ax
269265 legend_parent .legend (handles , labels , ** legend_kwargs )
270- return ax
266+
267+ if filepath is not None :
268+ if ani is None :
269+ fig .savefig (filepath )
270+ else :
271+ if not isinstance (filepath , Path ):
272+ filepath = Path (filepath )
273+ if filepath .suffix in {'.htm' , '.html' }:
274+ # TODO: we should offer the option to use to_jshtml instead of to_html5_video. Even if it makes the
275+ # files (much) bigger (because they are stored as individual frames) it also adds some useful
276+ # play/pause/next frame/... buttons.
277+ filepath .write_text (f'<html>{ ani .to_html5_video ()} </html>' , encoding = 'utf8' )
278+ else :
279+ writer = anim_kwargs .pop ('writer' , None )
280+ fps = anim_kwargs .pop ('fps' , 5 )
281+ metadata = anim_kwargs .pop ('metadata' , None )
282+ bitrate = anim_kwargs .pop ('bitrate' , None )
283+ if writer is None :
284+ # pillow only supports .gif, .png and .tiff ffmpeg supports .avi, .mov, .mp4 (but needs the
285+ # ffmpeg package installed)
286+ writer = 'pillow' if filepath .suffix in {'.gif' , '.png' , '.tiff' } else 'ffmpeg'
287+ from matplotlib .animation import writers
288+ if not writers .is_available (writer ):
289+ raise Exception (f"Cannot write animation using '{ filepath .suffix } ' extension "
290+ f"because '{ writer } ' writer is not available.\n "
291+ "Installing an optional package is probably necessary." )
292+
293+ ani .save (filepath , writer = writer , fps = fps , metadata = metadata , bitrate = bitrate )
294+
295+ if show :
296+ # The following line displays the plot window. Note however that
297+ # it is only blocking when no Qt loop is already running (i.e. we are
298+ # not running inside the editor).
299+ # When using the Qt backend this boils down to:
300+ # manager = fig.canvas.manager
301+ # manager.show()
302+ # if block:
303+ # manager.start_main_loop()
304+ # the last line just gets the current Qt QApplication instance (created during
305+ # the first canvas creation) and .exec() it
306+ plt .show (block = True )
307+ # It is important to return ani, because otherwise in the non-blocking case
308+ # (i.e. when run in the editor), the animation is garbage-collected before
309+ # it is drawn, and we get a blank animation.
310+ return (ax_to_return , ani ) if ani is not None else ax_to_return
311+ elif filepath is not None : # filepath and not show
312+ plt .close (fig )
313+ return None
314+ else : # no filepath and not show
315+ return (ax_to_return , ani ) if ani is not None else ax_to_return
271316
272317 def _plot_many (self , array , ax , kwargs , series_axes , subplot_axes , title , x , y ):
273318 if len (subplot_axes ):
0 commit comments