cdxcore 0.1.30__py3-none-any.whl → 0.1.32__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cdxcore might be problematic. Click here for more details.

cdxcore/dynaplot.py CHANGED
@@ -7,14 +7,16 @@ animated visualization with :mod:`matplotlib`,
7
7
  for example during training with machine learing kits such as
8
8
  *pyTorch*.
9
9
 
10
+ It also makes the creation of subplots more streamlined.
11
+
10
12
  This has been tested with Anaconda's
11
13
  JupyterHub and ``%matplotlib inline``.
12
14
 
13
15
  Overview
14
16
  --------
15
17
 
16
-
17
- It also makes the creation of subplots more streamlined.
18
+ Animated Graphs, Simple
19
+ ^^^^^^^^^^^^^^^^^^^^^^^
18
20
 
19
21
  The package now contains a lazy method to manage updates to graphs (animations).
20
22
  This is implemented as follows:
@@ -37,12 +39,56 @@ This is implemented as follows:
37
39
 
38
40
  If you do not call close, you will likely see
39
41
  unwanted copies of your plots in Jupyter.
42
+
43
+ If possible, use the ``with figure(...) as fig`` pattern which will ennsure that :meth:`cdxcore.dynaplot.DynaFig.close`
44
+ is called.
40
45
 
41
46
  Here is an example of animated line plots using :func:`cdxcore.dynaplot.DynaFig.store`::
42
47
 
43
48
  %matplotlib inline
44
49
  import numpy as np
45
- from cdxcore.dynaplot import figure # 'figure' is an alias for DynaFig
50
+ import time
51
+ from cdxcore.dynaplot import figure, MODE
52
+
53
+ x = np.linspace(0,1,100)
54
+ pm = 0.2
55
+
56
+ with figure(col_size=10) as fig:
57
+ ax = fig.add_subplot()
58
+ ax2 = fig.add_subplot()
59
+ ax2.sharey(ax)
60
+ store = fig.store()
61
+ fig.render()
62
+
63
+ for i in range(10):
64
+ y = np.random.random(size=(100,))
65
+ ax.set_title(f"Test {i}")
66
+ ax2.set_title(f"Test {i}")
67
+
68
+ store.remove() # delete all prviously stored elements
69
+ store += ax.plot(x,y,":", label=f"data {i}")
70
+ store += ax2.plot(x,y,"-",color="red", label=f"data {i}")
71
+ store += ax2.fill_between( x, y-pm, y+pm, color="blue", alpha=0.2 )
72
+ store += ax.legend()
73
+
74
+ fig.render()
75
+ time.sleep(0.5)
76
+
77
+ .. image:: /_static/dynaplot.gif
78
+
79
+ In above example we used the ``with`` context of a :class:`cdxcore.dynaplot.DynaFig` figure.
80
+ The point of using ``with`` where convenient is that it will call :meth:`cdxcore.dynaplot.DynaFig.close`
81
+ which will avoid duplicate copies of the figure in jupyter.
82
+
83
+ To do close the figure manually, call :meth:`cdxcore.dynaplot.DynaFig.close` directy:
84
+
85
+ .. code-block:: python
86
+ :emphasize-lines: 9, 30
87
+
88
+ %matplotlib inline
89
+ import numpy as np
90
+ import time
91
+ from cdxcore.dynaplot import figure, MODE
46
92
 
47
93
  x = np.linspace(0,1,100)
48
94
  pm = 0.2
@@ -51,17 +97,15 @@ Here is an example of animated line plots using :func:`cdxcore.dynaplot.DynaFig.
51
97
  ax = fig.add_subplot()
52
98
  ax2 = fig.add_subplot()
53
99
  ax2.sharey(ax)
54
- store = fig.store()
55
-
100
+ store = fig.store()
56
101
  fig.render()
57
102
 
58
- import time
59
- for i in range(5):
103
+ for i in range(10):
60
104
  y = np.random.random(size=(100,))
61
105
  ax.set_title(f"Test {i}")
62
106
  ax2.set_title(f"Test {i}")
63
107
 
64
- store.remove() # delete all previously stored elements
108
+ store.remove() # delete all prviously stored elements
65
109
  store += ax.plot(x,y,":", label=f"data {i}")
66
110
  store += ax2.plot(x,y,"-",color="red", label=f"data {i}")
67
111
  store += ax2.fill_between( x, y-pm, y+pm, color="blue", alpha=0.2 )
@@ -69,48 +113,43 @@ Here is an example of animated line plots using :func:`cdxcore.dynaplot.DynaFig.
69
113
 
70
114
  fig.render()
71
115
  time.sleep(0.5)
72
- fig.close()
73
-
74
- .. image:: /_static/dynaplot.gif
75
-
76
- This example shows the use of ``store`` with different elements.
116
+
117
+ fig.close()
77
118
 
78
119
  Here is an example with
79
120
  animated 3D plots, calling :meth:`matplotlib.axes.Axes.remove` manually::
80
121
 
81
122
  %matplotlib inline
82
123
  import numpy as np
83
- from cdxcore.dynaplot import figure # 'figure' is an alias for DynaFig
124
+ from cdxcore.dynaplot import figure
84
125
  import math
85
126
 
86
127
  x = np.linspace(0.,2.*math.pi,51)
87
128
  y = x
88
129
 
89
- fig = figure()
90
- ax1 = fig.add_subplot(projection='3d')
91
- ax2 = fig.add_subplot(projection='3d')
92
- ax1.set_xlim(0.,2.*math.pi)
93
- ax1.set_ylim(0.,2.*math.pi)
94
- ax1.set_zlim(-2,+2)
95
- ax1.set_title("Color specified")
96
- ax2.set_xlim(0.,2.*math.pi)
97
- ax2.set_ylim(0.,2.*math.pi)
98
- ax2.set_zlim(-2,+2)
99
- ax2.set_title("Color not specified")
100
- fig.render()
101
- r1 = None
102
- r2 = None
103
- import time
104
- for i in range(50):
105
- time.sleep(0.01)
106
- z = np.cos( float(i)/10.+x )+np.sin( float(i)/2.+y )
107
- if not r1 is None: r1.remove()
108
- if not r2 is None: r2.remove()
109
- r1 = ax1.scatter( x,y,z, color="blue" )
110
- r2 = ax2.scatter( 2.*math.pi-x,math.pi*(1.+np.sin( float(i)/2.+y )),z )
130
+ with figure() as fig:
131
+ ax1 = fig.add_subplot(projection='3d')
132
+ ax2 = fig.add_subplot(projection='3d')
133
+ ax1.set_xlim(0.,2.*math.pi)
134
+ ax1.set_ylim(0.,2.*math.pi)
135
+ ax1.set_zlim(-2,+2)
136
+ ax1.set_title("Color specified")
137
+ ax2.set_xlim(0.,2.*math.pi)
138
+ ax2.set_ylim(0.,2.*math.pi)
139
+ ax2.set_zlim(-2,+2)
140
+ ax2.set_title("Color not specified")
111
141
  fig.render()
112
- fig.close()
113
- print("/done")
142
+ r1 = None
143
+ r2 = None
144
+ import time
145
+ for i in range(50):
146
+ time.sleep(0.01)
147
+ z = np.cos( float(i)/10.+x )+np.sin( float(i)/2.+y )
148
+ if not r1 is None: r1.remove()
149
+ if not r2 is None: r2.remove()
150
+ r1 = ax1.scatter( x,y,z, color="blue" )
151
+ r2 = ax2.scatter( 2.*math.pi-x,math.pi*(1.+np.sin( float(i)/2.+y )),z )
152
+ fig.render()
114
153
 
115
154
  .. image:: /_static/dynaplot3D.gif
116
155
 
@@ -145,24 +184,35 @@ The example also shows that we can specify titles for subplots and figures easil
145
184
 
146
185
  %matplotlib inline
147
186
  import numpy as np
148
- from cdxcore.dynaplot import figure # 'figure' is an alias for DynaFig
149
-
150
- fig = figure(title="Multi Graph", columns=4)
151
- ref_ax = None
187
+ import time
188
+ from cdxcore.dynaplot import figure
189
+
152
190
  x = np.linspace(0,1,100)
153
- for k in range(9):
154
- ax = fig.add_subplot(f"Test {k}")
155
- y = np.random.random(size=(100,1))
156
- ax.plot(x,y,":",color="red", label="data")
157
- ax.legend(loc="upper left")
158
-
159
- if not ref_ax is None:
160
- ax.sharey(ref_ax)
161
- ax.sharex(ref_ax)
162
- else:
163
- ref_ax = ax
164
- fig.close()
165
-
191
+
192
+ with figure(title="Multi Graph", fig_size=(10,6), columns=4) as fig:
193
+ lines = []
194
+ ref_ax = None
195
+ for k in range(9):
196
+ ax = fig.add_subplot()
197
+ ax.set_title("Test %ld" % k)
198
+ y = np.random.random(size=(100,1))
199
+ l = ax.plot(x,y,":",color="red", label="data")
200
+ lines.append(l)
201
+ ax.legend()
202
+
203
+ if not ref_ax is None:
204
+ ax.sharey(ref_ax)
205
+ ax.sharex(ref_ax)
206
+ else:
207
+ ref_ax = ax
208
+ fig.render()
209
+
210
+ for i in range(5):
211
+ time.sleep(0.2)
212
+ for l in lines:
213
+ y = np.random.random(size=(100,1))
214
+ l[0].set_ydata( y )
215
+ fig.render()
166
216
  .. image:: /_static/multi.gif
167
217
 
168
218
  Grid Spec
@@ -178,18 +228,16 @@ Example::
178
228
  %matplotlib inline
179
229
  from cdxcore.dynaplot import figure
180
230
  import numpy as np
181
- x = np.linspace(-2.,+2,101)
182
- y = np.tanh(x)
183
- fig = figure("Grid Spec Example", figsize=(10,5))
184
- gs = fig.add_gridspec(2,2)
185
-
186
- ax = fig.add_subplot("1", spec_pos=gs[0,0] )
187
- ax.plot(x,y)
188
- ax = fig.add_subplot("2", spec_pos=gs[:,1] )
189
- ax.plot(x,y)
190
- ax = fig.add_subplot("3", spec_pos=gs[1,0] )
191
- ax.plot(x,y)
192
- fig.close()
231
+
232
+ x = np.linspace(-2.,+2,21)
233
+
234
+ with figure(tight=False) as fig:
235
+ ax = fig.add_subplot()
236
+ ax.plot( x, np.sin(x) )
237
+ fig.render()
238
+
239
+ ax = fig.add_axes( (0.5,0.5,0.3,0.3), "axes" )
240
+ ax.plot( x, np.cos(x) )
193
241
 
194
242
  .. image:: /_static/gridspec.gif
195
243
 
@@ -211,48 +259,59 @@ Example of using the same colors by order in two plots::
211
259
 
212
260
  x = np.linspace(0.,2.*math.pi,51)
213
261
 
214
- fig = figure(fig_size=(14,6))
215
- ax = fig.add_subplot("Sin")
216
- store = fig.store()
217
- # draw 10 lines in the first sub plot, and add a legend
218
- for i in range(10):
219
- y = np.sin(x/(i+1))
220
- ax.plot( x, y, color=color_base(i), label=f"f(x/{i+1})" )
221
- ax.legend(loc="lower right")
262
+ with figure(fig_size=(14,6)) as fig:
263
+ ax = fig.add_subplot("Sin")
264
+ store = fig.store()
265
+ # draw 10 lines in the first sub plot, and add a legend
266
+ for i in range(10):
267
+ y = np.sin(x/(i+1))
268
+ ax.plot( x, y, color=color_base(i), label=f"f(x/{i+1})" )
269
+ ax.legend(loc="lower right")
222
270
 
223
- # draw 10 lines in the second sub plot.
224
- # use the same colors for the same scaling of 'x'
225
- ax = fig.add_subplot("Cos")
271
+ # draw 10 lines in the second sub plot.
272
+ # use the same colors for the same scaling of 'x'
273
+ ax = fig.add_subplot("Cos")
226
274
 
227
- for i in range(10):
228
- z = np.cos(x/(i+1))
229
- store += ax.plot( x, z, color=color_base(i) )
230
- fig.render()
231
-
232
- # animiate, again with the same colors
233
- for p in np.linspace(0.,4.,11,endpoint=False):
234
- time.sleep(0.1)
235
- store.clear() # alias for 'remove'
236
275
  for i in range(10):
237
- z = np.cos((x+p)/(i+1))
276
+ z = np.cos(x/(i+1))
238
277
  store += ax.plot( x, z, color=color_base(i) )
239
278
  fig.render()
240
279
 
241
- fig.close()
242
-
280
+ # animiate, again with the same colors
281
+ for p in np.linspace(0.,4.,11,endpoint=False):
282
+ time.sleep(0.1)
283
+ store.clear() # alias for 'remove'
284
+ for i in range(10):
285
+ z = np.cos((x+p)/(i+1))
286
+ store += ax.plot( x, z, color=color_base(i) )
287
+ fig.render()
288
+
243
289
  .. image:: /_static/colors.gif
244
290
 
245
- Here is a view of the first 20 colors of the four supported maps:
291
+ Here is a view of the first 20 colors of the four supported maps, computed with::
246
292
 
293
+ %matplotlib inline
294
+ from cdxcore.dynaplot import figure, color_names, color
295
+ import numpy as np
296
+ x = np.linspace(-2.*np.pi,+2*np.pi,101)
297
+ N = 20
298
+ with figure(f"Color tables up to #{N}", figsize=(20,15), columns=2) as fig:
299
+ for color_name in color_names:
300
+ ax = fig.add_subplot(color_name)
301
+ for i in range(N):
302
+ r =1./(i+1)
303
+ ax.plot( x, np.sin(x*r), color
304
+
247
305
  .. image:: /_static/colormap.gif
248
306
 
249
307
  The classes :class:`cdxcore.dynaplot.colors_css4`, :class:`cdxcore.dynaplot.colors_base`, :class:`cdxcore.dynaplot.colors_tableau`, :class:`cdxcore.dynaplot.colors_xkcd`
250
308
  are generators for the same colors.
251
309
 
310
+
252
311
  Known Issues
253
312
  ^^^^^^^^^^^^
254
313
 
255
- Some users reported that the package does not work in some versions of Jupyter, in particular with VSCode.
314
+ Some users reported that the package does not update figures consistently in some versions of Jupyter, in particular with VSCode.
256
315
  In this case, please try changing the ``draw_mode`` parameter when calling :func:`cdxcore.dynaplot.figure`.
257
316
 
258
317
  Import
@@ -273,6 +332,7 @@ from IPython import display
273
332
  import io as io
274
333
  import gc as gc
275
334
  import types as types
335
+ import numpy as np
276
336
  from collections.abc import Collection
277
337
  from .deferred import Deferred
278
338
  from .util import verify, warn
@@ -288,20 +348,18 @@ class MODE:
288
348
  """ Call :func:`IPython.display.display`. """
289
349
 
290
350
  CANVAS_IDLE = 0x02
291
- """ Call :meth:`matplotlib.pyplot.figure.canvas.draw_idle`. """
351
+ """ Call `matplotlib.pyplot.figure.canvas.draw_idle <https://matplotlib.org/stable/api/backend_bases_api.html#matplotlib.backend_bases.FigureCanvasBase.draw_idle>`__ """
292
352
 
293
353
  CANVAS_DRAW = 0x04
294
- """ Call :meth:`matplotlib.pyplot.figure.canvas.draw`. """
354
+ """ Call `matplotlib.pyplot.figure.canvas.draw_idle <https://matplotlib.org/stable/api/backend_bases_api.html#matplotlib.backend_bases.FigureCanvasBase.draw>`__ """
295
355
 
296
356
  PLT_SHOW = 0x80
297
357
  """ Call :func:`matplotlib.pyplot.show`. """
298
358
 
299
- JUPYTER = HDISPLAY
300
- """ Setting which works for Jupyter lab as far as we can tell. """
301
-
302
- VSCODE = HDISPLAY|PLT_SHOW
303
- """ Setting which works for VSCode it seems. Feedback welome. """
304
-
359
+ DEFAULT = HDISPLAY
360
+ """ Setting which works for Jupyter lab and VSCode with ``%matplotlib inline`` as far as we can tell. """
361
+
362
+
305
363
  class _DynaDeferred( Deferred ):
306
364
  """ Internal class which implements the required deferral method """
307
365
  __setitem__ = Deferred._deferred_handle("__setitem__", num_args=2, fmt="{parent}[{arg0}]={arg1}")
@@ -315,9 +373,9 @@ class _DynaDeferred( Deferred ):
315
373
 
316
374
  class DynaAx(_DynaDeferred):
317
375
  """
318
- Deferred wrapper around a :class:`matplotlib.pyplot.axis` objects returned by :meth:`cdxcore.dynaplot.DynaFig.add_subplot` or similar.
376
+ Deferred wrapper around a :class:`matplotlib.axes.Axes` objects returned by :meth:`cdxcore.dynaplot.DynaFig.add_subplot` or similar.
319
377
 
320
- *You should not need to know that this object is not actually a* :class:`matplotlib.pyplot.axis`.
378
+ *You should not need to know that this object is not actually a* :class:`matplotlib.axes.Axes`.
321
379
  *If you receive error messages which you do not understand, please contact the authors of this
322
380
  module.*
323
381
  """
@@ -439,17 +497,17 @@ class DynaAx(_DynaDeferred):
439
497
  self.fig_list.remove(self)
440
498
  self.ax.remove()
441
499
  self.ax = None
442
- gc.collect()
500
+ #gc.collect()
443
501
 
444
502
  # automatic limit handling
445
503
  # -------------------------
446
504
 
447
505
  def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs ):
448
506
  """
449
- Wrapper around :func:`matplotlib.axes.plot`.
507
+ Wrapper around :func:`matplotlib.axes.Axes.plot`.
450
508
 
451
509
  This function wrapper does not support the ``data`` interface
452
- of :func:`matplotlib.axes.plot`.
510
+ of :func:`matplotlib.axes.Axes.plot`.
453
511
 
454
512
  If automatic limits are not used, this is a wrapper with deferred pass-through.
455
513
  If automatic limits are used, then this function will update
@@ -576,37 +634,44 @@ class DynaFig(_DynaDeferred):
576
634
  See :func:`cdxcore.dynaplot.figure` for more information.
577
635
  """
578
636
 
579
- def __init__(self, title : str = None, *,
637
+ def __init__(self, title : str|None = None, *,
580
638
  row_size : int = 5,
581
639
  col_size : int = 4,
582
- fig_size : tuple[int] = None,
640
+ fig_size : tuple[int]|None = None,
583
641
  columns : int = 5,
584
642
  tight : bool = True,
585
- draw_mode: int = MODE.JUPYTER,
643
+ draw_mode: int = MODE.DEFAULT,
586
644
  **fig_kwargs ):
587
645
  """
588
-
646
+ __init__
589
647
  """
590
- self.hdisplay = None
591
- self.axes = [] #: List of axes. Until; :meth:`cdxcore.dynaplot.DynaFig.render` is called, these are :class:`cdxcore.dynaplot.DynaAx` objects;
592
- #: afterwards, these are :class:`matplotlib.pyplot.axis` objects.
593
- self.grid_specs = []
594
- self.fig = None
595
- self.row_size = int(row_size)
596
- self.col_size = int(col_size)
597
- self.cols = int(columns)
598
- self.tight = bool(tight)
599
- self.tight_para = None
600
- self.fig_kwargs = dict(fig_kwargs)
601
- if self.tight:
602
- self.fig_kwargs['tight_layout'] = True
603
- verify( self.row_size > 0 and self.col_size > 0 and self.cols > 0, "Invalid input.", exception=ValueError)
604
- self.this_row = 0
605
- self.this_col = 0
606
- self.max_col = 0
607
- self.fig_title = title
608
- self.closed = False
609
- self.draw_mode = draw_mode
648
+
649
+ self._hdisplay = None
650
+ self._axes = [] #:
651
+ #:
652
+ self._grid_specs = []
653
+ self._fig = None
654
+ self._row_size = int(row_size)
655
+ self._col_size = int(col_size)
656
+ self._cols = int(columns)
657
+ self._tight = bool(tight)
658
+ self._tight_para = None
659
+ self._fig_kwargs = dict(fig_kwargs)
660
+ if self._tight:
661
+ self._fig_kwargs['tight_layout'] = True
662
+ verify( self._row_size > 0 and self._col_size > 0 and self._cols > 0, "Invalid input.", exception=ValueError)
663
+ self._this_row = 0
664
+ self._this_col = 0
665
+ self._max_col = 0
666
+ self._fig_title = title
667
+ self._closed = False
668
+ self._enter_count = 0
669
+ self.draw_mode = draw_mode
670
+ #: A combination of :class:`cdxcore.dynaplot.MODE` flags on how to draw plots
671
+ #: once they were rendered. The required function call differs by IPython platform.
672
+ #: The default, :attr:`cdxcore.dynaplot.MODE.DEFAULT` draws well on Jupyter notebooks
673
+ #: and VSCode if :func:`cdxcore.dynaplot.dynamic_backend` was called
674
+ #: (which sets ``%matplotlib inline``).
610
675
 
611
676
  verify( not 'cols' in fig_kwargs, "Unknown keyword 'cols'. Did you mean 'columns'?", exception=ValueError)
612
677
 
@@ -614,7 +679,7 @@ class DynaFig(_DynaDeferred):
614
679
  verify( not 'figsize' in fig_kwargs, "Cannot specify both `figsize` and `fig_size`", exception=ValueError)
615
680
  fig_kwargs['figsize'] = fig_size
616
681
 
617
- dyna_title = title if len(title) <= 20 else ( title[:17] + "..." ) if not title is None else None
682
+ dyna_title = ( title if len(title) <= 20 else ( title[:17] + "..." ) ) if not title is None else None
618
683
  _DynaDeferred.__init__(self, f"figure('{dyna_title}')" if not title is None else "figure()" )
619
684
 
620
685
  def __str__(self):
@@ -624,10 +689,45 @@ class DynaFig(_DynaDeferred):
624
689
  """ Ensure the figure is closed """
625
690
  self.close()
626
691
 
627
- def add_subplot(self, title : str = None, *,
628
- new_row : bool = None,
629
- spec_pos : type = None,
630
- projection: str = None,
692
+ # properties
693
+ # ----------
694
+
695
+ @property
696
+ def axes(self) -> list[DynaAx]:
697
+ """
698
+ List of axes. Until :meth:`cdxcore.dynaplot.DynaFig.render` is called, these are :class:`cdxcore.dynaplot.DynaAx` objects;
699
+ afterwards, these are :class:`matplotlib.axes.Axes` objects.
700
+ """
701
+ return self._axes
702
+
703
+ @property
704
+ def fig(self) -> plt.Figure|None:
705
+ """
706
+ Returns the figure or ``None`` if it was not yet rendered.
707
+ """
708
+ return self._fig
709
+
710
+ @property
711
+ def hdisplay(self) -> display.DisplayHandle|None:
712
+ """
713
+ Returns the :class:`IPython.display.DisplayHandle` for the current display, if
714
+ ``MODE.HDISPLAY`` was used for ``draw_mode`` when the figure was constructed, and if the figure
715
+ was rendered yet. Otherwise returns ``None``.
716
+ """
717
+ return self._hdisplay
718
+
719
+ @property
720
+ def is_closed(self) -> bool:
721
+ """ Returrns whether the figure was closed """
722
+ return self._closed
723
+
724
+ # functionality
725
+ # -------------
726
+
727
+ def add_subplot(self, title : str|None = None, *,
728
+ new_row : bool|None = None,
729
+ spec_pos : type|None = None,
730
+ projection: str|None = None,
631
731
  **kwargs) -> DynaAx:
632
732
  """
633
733
  Adds a subplot.
@@ -640,29 +740,35 @@ class DynaFig(_DynaDeferred):
640
740
 
641
741
  This function returns a wrapper which defers the creation of the actual sub plot until
642
742
  :meth:`cdxcore.dynaplot.DynaFig.render` or :meth:`cdxcore.dynaplot.DynaFig.close` is called.
743
+
643
744
  Thus this function cannot be called after :meth:`cdxcore.dynaplot.DynaFig.render` was called as then the geometry of the plots
644
- is set. Use :meth:`cdxcore.dynaplot.DynaFig.add_axes` to draw plots in any position.
745
+ is set. Use :meth:`cdxcore.dynaplot.DynaFig.add_axes` to draw plots at any time/.
645
746
 
646
747
  Parameters
647
748
  ----------
648
- title : str, optional
749
+ title : str | None, default ``None``
649
750
  Optional title for the plot.
650
751
 
651
- new_row : bool, optional
752
+ new_row : bool | None, default ``None``
652
753
  Whether to force a new row and place this polt in the first column. Default is ``False``.
653
754
 
654
- spec_pos : optional
655
- Grid spec position, or ``None``.
755
+ spec_pos : grid spec | None, default ``None``
756
+ Grid spec position from :meth:`cdxcore.dynaplot.DynaFig.add_gridspec`, or ``None``.
656
757
 
657
- projection : str, optional
758
+ projection : str | None, default ``None``
658
759
  What ``projection`` to use. The default ``None`` matches the default choice for
659
760
  :meth:`matplotlib.figure.Figure.add_subplot`.
660
761
 
661
762
  kwargs : dict
662
763
  other arguments to be passed to matplotlib's :meth:`matplotlib.figure.Figure.add_subplot`.
764
+
765
+ Returns
766
+ -------
767
+ Axis : :class:`cdxcore.dynaplot.DynaAx`
768
+ A wrapper around an matplotlib axis.
663
769
  """
664
- verify( not self.closed, "Cannot call add_subplot() after close() was called")
665
- verify( self.fig is None, "Cannot call add_subplot() after render() was called. Use add_axes() instead")
770
+ verify( not self._closed, "Cannot call add_subplot() after close() was called")
771
+ verify( self._fig is None, "Cannot call add_subplot() after render() was called. Use add_axes() instead")
666
772
 
667
773
  # backward compatibility:
668
774
  # previous versions has "new_row" first.
@@ -672,7 +778,7 @@ class DynaFig(_DynaDeferred):
672
778
  if not spec_pos is None:
673
779
  assert new_row is None, ("Cannot specify 'new_row' when 'spec_pos' is specified")
674
780
  ax = DynaAx( fig_id=hash(self),
675
- fig_list=self.axes,
781
+ fig_list=self._axes,
676
782
  row=None,
677
783
  col=None,
678
784
  title=title,
@@ -683,22 +789,22 @@ class DynaFig(_DynaDeferred):
683
789
 
684
790
  else:
685
791
  new_row = bool(new_row) if not new_row is None else False
686
- if (self.this_col >= self.cols) or ( new_row and not self.this_col == 0 ):
687
- self.this_col = 0
688
- self.this_row = self.this_row + 1
689
- if self.max_col < self.this_col:
690
- self.max_col = self.this_col
792
+ if (self._this_col >= self._cols) or ( new_row and not self._this_col == 0 ):
793
+ self._this_col = 0
794
+ self._this_row = self._this_row + 1
795
+ if self._max_col < self._this_col:
796
+ self._max_col = self._this_col
691
797
  ax = DynaAx( fig_id=hash(self),
692
- fig_list=self.axes,
693
- row=self.this_row,
694
- col=self.this_col,
798
+ fig_list=self._axes,
799
+ row=self._this_row,
800
+ col=self._this_col,
695
801
  spec_pos=None,
696
802
  rect=None,
697
803
  title=title,
698
804
  projection=projection,
699
805
  kwargs=dict(kwargs) )
700
- self.this_col += 1
701
- assert ax in self.axes
806
+ self._this_col += 1
807
+ assert ax in self._axes
702
808
  return ax
703
809
 
704
810
  add_plot = add_subplot
@@ -706,7 +812,7 @@ class DynaFig(_DynaDeferred):
706
812
  def add_axes( self,
707
813
  rect : tuple,
708
814
  title : str|None = None, *,
709
- projection: str = None,
815
+ projection: str|None = None,
710
816
  **kwargs ) -> DynaAx:
711
817
  """
712
818
  Add a freely placed sub plot.
@@ -726,22 +832,31 @@ class DynaFig(_DynaDeferred):
726
832
  The dimensions (left, bottom, width, height) of the new plot.
727
833
  All quantities are in fractions of figure width and height.
728
834
 
729
- title : str, optional
835
+ title : str | None, default ``None``
730
836
  Title for the plot, or ``None`` for no plot.
731
837
 
732
- projection : str, optional
838
+ projection : str | None, default ``None``
733
839
  What ``projection`` to use. The default ``None`` matches the default choice for
734
840
  :meth:`matplotlib.figure.Figure.add_axes`
735
-
841
+
736
842
  args, kwargs :
737
843
  keyword arguments to be passed to :meth:`matplotlib.figure.Figure.add_axes`.
738
- """
739
- verify( not self.closed, "Cannot call add_subplot() after close() was called")
740
844
 
845
+ Returns
846
+ -------
847
+ Axis : :class:`cdxcore.dynaplot.DynaAx`
848
+ A wrapper around an matplotlib axis.
849
+ """
850
+ verify( not self._closed, "Cannot call add_subplot() after close() was called")
851
+ verify( not isinstance(rect,str), "'rect' is a string ... did you mix up the order of 'title' and 'rect'?")
741
852
  title = str(title) if not title is None else None
853
+ if not isinstance(rect, tuple):
854
+ rect = tuple(rect)
855
+ verify( len(rect)==4, lambda:f"'rect' must be a tuple of length 4. Found '{rect}'")
856
+ verify( np.isfinite(rect).all(), lambda:f"'rect' has infinite elements: {rect}")
742
857
 
743
858
  ax = DynaAx( fig_id=hash(self),
744
- fig_list=self.axes,
859
+ fig_list=self._axes,
745
860
  row=None,
746
861
  col=None,
747
862
  title=title,
@@ -749,17 +864,17 @@ class DynaFig(_DynaDeferred):
749
864
  rect=rect,
750
865
  projection=projection,
751
866
  kwargs=dict(kwargs) )
752
- assert ax in self.axes
753
- if not self.fig is None:
754
- ax._initialize( self.fig, rows=None, cols=None )
867
+ assert ax in self._axes
868
+ if not self._fig is None:
869
+ ax._initialize( self._fig, rows=None, cols=None )
755
870
  return ax
756
871
 
757
872
  def add_gridspec(self, ncols=1, nrows=1, **kwargs):
758
873
  """
759
874
  Wrapper for :meth:`matplotlib.figure.Figure.add_gridspec`, returning a defered ``GridSpec``.
760
875
  """
761
- grid = _DynaGridSpec( ncols=ncols, nrows=nrows, cnt=len(self.grid_specs), kwargs=kwargs )
762
- self.grid_specs.append( grid )
876
+ grid = _DynaGridSpec( ncols=ncols, nrows=nrows, cnt=len(self._grid_specs), kwargs=kwargs )
877
+ self._grid_specs.append( grid )
763
878
  return grid
764
879
 
765
880
  def next_row(self):
@@ -769,11 +884,11 @@ class DynaFig(_DynaDeferred):
769
884
  The next plot generated by :meth:`cdxcore.dynaplot.DynaFig.add_subplot` will
770
885
  appears in the first column of the next row.
771
886
  """
772
- verify( self.fig is None, "Cannot call next_row() after render() was called")
773
- if self.this_col == 0:
887
+ verify( self._fig is None, "Cannot call next_row() after render() was called")
888
+ if self._this_col == 0:
774
889
  return
775
- self.this_col = 0
776
- self.this_row = self.this_row + 1
890
+ self._this_col = 0
891
+ self._this_row = self._this_row + 1
777
892
 
778
893
  def render(self, draw : bool = True ):
779
894
  """
@@ -786,47 +901,47 @@ class DynaFig(_DynaDeferred):
786
901
 
787
902
  Parameters
788
903
  ----------
789
- draw : bool
790
- If False, then the figure is created, but not drawn.
791
- You usually use ``False`` when planning to use
792
- :func:`cdxcore.dynaplot.DynaFig.savefig` or :func:`cdxcore.dynaplot..DynaFig.to_bytes`.
904
+ draw : bool, default ``True``
905
+ If False, then the figure is created, but not drawn.
906
+ You usually use ``False`` when planning to use
907
+ :func:`cdxcore.dynaplot.DynaFig.savefig` or :func:`cdxcore.dynaplot.DynaFig.to_bytes`.
793
908
  """
794
- verify( not self.closed, "Cannot call render() after close() was called")
795
- if len(self.axes) == 0:
909
+ verify( not self._closed, "Cannot call render() after close() was called")
910
+ if len(self._axes) == 0:
796
911
  return
797
- if self.fig is None:
912
+ if self._fig is None:
798
913
  # create figure
799
- if not 'figsize' in self.fig_kwargs:
800
- self.fig_kwargs['figsize'] = ( self.col_size*(self.max_col+1), self.row_size*(self.this_row+1))
801
- self.fig = plt.figure( **self.fig_kwargs )
802
- if self.tight:
803
- self.fig.tight_layout()
804
- self.fig.set_tight_layout(True)
805
- if not self.fig_title is None:
806
- self.fig.suptitle( self.fig_title )
914
+ if not 'figsize' in self._fig_kwargs:
915
+ self._fig_kwargs['figsize'] = ( self._col_size*(self._max_col+1), self._row_size*(self._this_row+1))
916
+ self._fig = plt.figure( **self._fig_kwargs )
917
+ if self._tight:
918
+ self._fig.tight_layout()
919
+ self._fig.set_tight_layout(True)
920
+ if not self._fig_title is None:
921
+ self._fig.suptitle( self._fig_title )
807
922
  # create all grid specs
808
- for gs in self.grid_specs:
809
- gs._initialize( self.fig )
923
+ for gs in self._grid_specs:
924
+ gs._initialize( self._fig )
810
925
  # create all axes
811
- for ax in self.axes:
812
- ax._initialize( self.fig, rows=self.this_row+1, cols=self.max_col+1 )
926
+ for ax in self._axes:
927
+ ax._initialize( self._fig, rows=self._this_row+1, cols=self._max_col+1 )
813
928
  # execute all deferred calls to fig()
814
- self.deferred_resolve( self.fig )
929
+ self.deferred_resolve( self._fig )
815
930
 
816
931
  if not draw:
817
932
  return
818
933
  if self.draw_mode & MODE.HDISPLAY:
819
- if self.hdisplay is None:
820
- self.hdisplay = display.display(display_id=True)
821
- verify( not self.hdisplay is None, "Could not optain current IPython display ID from IPython.display.display(). Set DynaFig.MODE = 'canvas' for an alternative mode")
822
- self.hdisplay.update(self.fig)
934
+ if self._hdisplay is None:
935
+ self._hdisplay = display.display(display_id=True)
936
+ verify( not self._hdisplay is None, "Could not optain current IPython display ID from IPython.display.display(). Set DynaFig.MODE = 'canvas' for an alternative mode")
937
+ self._hdisplay.update(self._fig)
823
938
  if self.draw_mode & MODE.CANVAS_IDLE:
824
- self.fig.canvas.draw_idle()
939
+ self._fig.canvas.draw_idle()
825
940
  if self.draw_mode & MODE.CANVAS_DRAW:
826
- self.fig.canvas.draw()
941
+ self._fig.canvas.draw()
827
942
  if self.draw_mode & MODE.PLT_SHOW:
828
943
  plt.show()
829
- gc.collect() # for some unknown reason this is required in VSCode
944
+ #gc.collect() # for some unknown reason this is required in VSCode
830
945
 
831
946
  def savefig(self, fname : str,
832
947
  silent_close : bool = True,
@@ -839,21 +954,21 @@ class DynaFig(_DynaDeferred):
839
954
 
840
955
  Parameters
841
956
  ----------
842
- fname : str
843
- `filename or file-like object <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html>`__
957
+ fname : str
958
+ `filename or file-like object <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html>`__
844
959
 
845
- silent_close : bool
846
- If ``True`` (the default), call :meth:`cdxcore.dynaplot.DynaFig,close` once the figure was saved to disk.
847
- Unless the figure was drawn before, this means that the figure will not be displayed in jupyter, and
848
- subsequent activity is blocked.
849
-
850
- kwargs : dict
851
- These arguments will be passed to :meth:`matplotlib.pyplot.savefig`.
960
+ silent_close : bool, default ``True``
961
+ If ``True`` (the default), call :meth:`cdxcore.dynaplot.DynaFig.close` once the figure was saved to disk.
962
+ Unless the figure was drawn before, this means that the figure will not be displayed in jupyter, and
963
+ subsequent activity is blocked.
964
+
965
+ kwargs : dict
966
+ These arguments will be passed to :meth:`matplotlib.pyplot.savefig`.
852
967
  """
853
- verify( not self.closed, "Cannot call savefig() after close() was called")
854
- if self.fig is None:
968
+ verify( not self._closed, "Cannot call savefig() after close() was called")
969
+ if self._fig is None:
855
970
  self.render(draw=False)
856
- self.fig.savefig( fname, **kwargs )
971
+ self._fig.savefig( fname, **kwargs )
857
972
  if silent_close:
858
973
  self.close(render=False)
859
974
 
@@ -870,21 +985,21 @@ class DynaFig(_DynaDeferred):
870
985
 
871
986
  Parameters
872
987
  ----------
873
- silent_close : bool, optional
874
- If ``True``, call :meth:`cdxcore.dynaplot.DynaFig,close` after this genersating the byte streanm.
875
- Unless the figure was drawn before, this means that the figure will not be displayed in jupyter, and
876
- subsequent activity is blocked.
988
+ silent_close : bool, default ``True``
989
+ If ``True``, call :meth:`cdxcore.dynaplot.DynaFig.close` after this genersating the byte streanm.
990
+ Unless the figure was drawn before, this means that the figure will not be displayed in jupyter, and
991
+ subsequent activity is blocked.
877
992
 
878
993
  Returns
879
994
  -------
880
- image : bytes
881
- Buyte stream of the image.
995
+ image : bytes
996
+ Buyte stream of the image.
882
997
  """
883
- verify( not self.closed, "Cannot call savefig() after close() was called")
998
+ verify( not self._closed, "Cannot call savefig() after close() was called")
884
999
  img_buf = io.BytesIO()
885
- if self.fig is None:
1000
+ if self._fig is None:
886
1001
  self.render(draw=False)
887
- self.fig.savefig( img_buf )
1002
+ self._fig.savefig( img_buf )
888
1003
  if silent_close:
889
1004
  self.close(render=False)
890
1005
  data = img_buf.getvalue()
@@ -902,41 +1017,36 @@ class DynaFig(_DynaDeferred):
902
1017
  Closes the figure.
903
1018
 
904
1019
  Call this to avoid a duplicate in jupyter output cells.
905
- By dault this function will call :meth:`cdxcore.dynaplot.DynaFig,render` to draw the figure, and then close it.
1020
+ By dault this function will call :meth:`cdxcore.dynaplot.DynaFig.render` to draw the figure, and then close it.
906
1021
 
907
1022
  Parameters
908
1023
  ----------
909
- render : bool, optional
910
- If `True``, the default, this function will call :meth:`cdxcore.dynaplot.DynaFig,render` and therefore renders the figure before closing the figure.
911
- clear :
1024
+ render : bool, default ``True``
1025
+ If ``True``, the default, this function will call :meth:`cdxcore.dynaplot.DynaFig,render` and therefore renders the figure before closing the figure.
1026
+ clear : bool, default ``False``
912
1027
  If ``True``, all axes will be cleared. *This is experimental.* The default is ``False``.
913
1028
  """
914
- if not self.closed:
1029
+ if not self._closed:
915
1030
  # magic wand to avoid printing an empty figure message
916
1031
  if clear:
917
- if not self.fig is None:
1032
+ if not self._fig is None:
918
1033
  def repr_magic(self):
919
- return type(self)._repr_html_(self) if len(self.axes) > 0 else "</HTML>"
920
- self.fig._repr_html_ = types.MethodType(repr_magic,self.fig)
921
- self.delaxes( self.axes, render=render )
1034
+ return type(self)._repr_html_(self) if len(self._axes) > 0 else "</HTML>"
1035
+ self._fig._repr_html_ = types.MethodType(repr_magic,self._fig)
1036
+ self.delaxes( self._axes, render=render )
922
1037
  elif render:
923
1038
  self.render(draw=True)
924
- if not self.fig is None:
925
- plt.close(self.fig)
926
- self.fig = None
927
- self.closed = True
928
- self.hdisplay = None
1039
+ if not self._fig is None:
1040
+ plt.close(self._fig)
1041
+ self._fig = None
1042
+ self._closed = True
1043
+ self._hdisplay = None
929
1044
  gc.collect()
930
1045
 
931
- def get_axes(self) -> list:
932
- """ Equivalent to ``self.axes`` """
933
- verify( not self.closed, "Cannot call render() after close() was called")
934
- return self.axes
935
-
936
1046
  def remove_all_axes(self, *, render : bool = False):
937
- """ Calles :meth:`cdxcore.dynalot.DynaAx.remove` for all axes """
938
- while len(self.axes) > 0:
939
- self.axes[0].remove()
1047
+ """ Calls :meth:`cdxcore.dynaplot.DynaAx.remove` for all :attr:`cdxcore.dynaplot.DynaFig.axes` """
1048
+ while len(self._axes) > 0:
1049
+ self._axes[0].remove()
940
1050
  if render:
941
1051
  self.render(draw=True)
942
1052
 
@@ -944,13 +1054,13 @@ class DynaFig(_DynaDeferred):
944
1054
  """
945
1055
  Equivalent of :func:`matplotlib.figure.Figure.delaxes`, but this function can also take a list.
946
1056
  """
947
- verify( not self.closed, "Cannot call render() after close() was called")
1057
+ verify( not self._closed, "Cannot call render() after close() was called")
948
1058
  if isinstance( ax, Collection ):
949
1059
  ax = list(ax)
950
1060
  for x in ax:
951
1061
  x.remove()
952
1062
  else:
953
- assert ax in self.axes, ("Cannot delete axes which wasn't created by this figure")
1063
+ assert ax in self._axes, ("Cannot delete axes which wasn't created by this figure")
954
1064
  ax.remove()
955
1065
  if render:
956
1066
  self.render()
@@ -959,24 +1069,27 @@ class DynaFig(_DynaDeferred):
959
1069
  # -----------------------
960
1070
 
961
1071
  def __enter__(self):
1072
+ self._enter_count += 1
962
1073
  return self
963
1074
 
964
1075
  def __exit__(self, *args, **kwargs):
965
- self.close()
1076
+ self._enter_count -= 1
1077
+ if self._enter_count <= 0:
1078
+ self.close()
966
1079
  return False
967
1080
 
968
- def figure( title : str = None, *,
1081
+ def figure( title : str|None = None, *,
969
1082
  row_size : int = 5,
970
1083
  col_size : int = 4,
971
- fig_size : tuple[int] = None,
1084
+ fig_size : tuple[int]|None = None,
972
1085
  columns : int = 5,
973
1086
  tight : bool = True,
974
- draw_mode: int = MODE.JUPYTER,
1087
+ draw_mode: int = MODE.DEFAULT,
975
1088
  **fig_kwargs ):
976
1089
  """
977
1090
  Creates a dynamic figure of type :class:`cdxcore.dynaplot.DynaFig`.
978
1091
 
979
- By default the ``fig_size`` of the underlying :class:`matplotlib.pyplot.figure`
1092
+ By default the ``fig_size`` of the underlying :class:`matplotlib.figure.Figure`
980
1093
  will be derived from the number of plots vs ``cols``, ``row_size`` and ``col_size``
981
1094
  as ``(col_size* (N%col_num), row_size (N//col_num))``.
982
1095
 
@@ -996,23 +1109,21 @@ def figure( title : str = None, *,
996
1109
  matplotlib need to pre-specify axes positions::
997
1110
 
998
1111
  from cdxcore.dynaplot import figure
999
- fig = dynaplot.figure("Two plots")
1000
- ax = fig.add_subplot("1")
1001
- ax.plot(x,y)
1002
- ax = fig.add_subplot("2")
1003
- ax.plot(x,y)
1004
- fig.render()
1112
+ with dynaplot.figure("Two plots") as fig:
1113
+ ax = fig.add_subplot("1")
1114
+ ax.plot(x,y)
1115
+ ax = fig.add_subplot("2")
1116
+ ax.plot(x,y)
1005
1117
 
1006
1118
  Here is an example using :meth:`matplotlib.figure.Figure.add_gridspec`::
1007
1119
 
1008
1120
  from cdxcore.dynaplot import figure
1009
- fig = dynaplot.figure()
1010
- gs = fig.add_gridspec(2,2)
1011
- ax = fig.add_subplot( gs[:,0] )
1012
- ax.plot(x,y)
1013
- ax = fig.add_subplot( gs[:,1] )
1014
- ax.plot(x,y)
1015
- fig.render()
1121
+ with dynaplot.figure() as fig:
1122
+ gs = fig.add_gridspec(2,2)
1123
+ ax = fig.add_subplot( gs[:,0] )
1124
+ ax.plot(x,y)
1125
+ ax = fig.add_subplot( gs[:,1] )
1126
+ ax.plot(x,y)
1016
1127
 
1017
1128
  **Important Functions:**
1018
1129
 
@@ -1040,39 +1151,43 @@ def figure( title : str = None, *,
1040
1151
 
1041
1152
  Parameters
1042
1153
  ----------
1043
- title : str, optional
1154
+ title : str, default ``None``
1044
1155
  An optional title which will be passed to :meth:`matplotlib.pyplot.suptitle`.
1045
1156
 
1046
- fig_size : tuple[int], optional
1157
+ fig_size : tuple[int] | None, default ``None``
1047
1158
  By default the ``fig_size`` of the underlying :class:`matplotlib.pyplot.figure`
1048
1159
  will be derived from the number of plots vs ``cols``, ``row_size`` and ``col_size``
1049
1160
  as ``(col_size* (N%col_num), row_size (N//col_num))``.
1050
1161
 
1051
1162
  If ``fig_size`` is specified then ``row_size`` and ``col_size`` are ignored.
1052
1163
 
1053
- row_size : int, optional
1164
+ row_size : int, default ``5``
1054
1165
  Size for a row for matplot lib. Default is 5.
1055
1166
  This is ignored if ``fig_size`` is specified.
1056
1167
 
1057
- col_size : int, optional
1168
+ col_size : int, default ``4``
1058
1169
  Size for a column for matplot lib. Default is 4.
1059
1170
  This is ignored if ``fig_size`` is specified.
1060
1171
 
1061
- columns : int, optional
1172
+ columns : int, default ``5``
1062
1173
  How many columns to use when :meth:`cdxcore.dynaplot.DynaFig.add_subplot` is used.
1063
1174
  If omitted then the default is 5.
1064
1175
 
1065
- tight : bool, optional
1176
+ tight : bool, default ``True``
1066
1177
  Short cut for :meth:`matplotlib.figure.Figure.set_tight_layout`. The default is ``True``.
1067
1178
 
1068
1179
  Note that when ``tight`` is ``True`` and :meth:`cdxcore.dynaplot.DynaFig.add_axes`
1069
1180
  is called a :class:`UserWarning` is generated. Turn ``tight`` off to avoid this.
1070
1181
 
1071
- draw_mode : int, optional
1182
+ draw_mode : int, default ``MODE.DEFAULT``
1072
1183
  A combination of :class:`cdxcore.dynaplot.MODE` flags on how to draw plots
1073
1184
  once they were rendered. The required function call differs by IPython platform.
1074
- The default, :attr:`cdxcore.dynaplot.MODE.JUPYTER` draws well on Jupyter notebooks.
1075
- For VSCode, you might need :attr:`cdxcore.dynaplot.MODE.VSCODE`.
1185
+ The default, :attr:`cdxcore.dynaplot.MODE.DEFAULT` draws well on Jupyter notebooks
1186
+ and in VSCode if ``%matplotlin inline`` or ``widget`` is used. The latter requires the
1187
+ packages ``ipympl`` and ``ipywidgets``.
1188
+
1189
+ Use the function :func:`cdxcore.dynaplot.dynamic_backend` to
1190
+ set the ``widget`` mode if possible.
1076
1191
 
1077
1192
  fig_kwargs :
1078
1193
  Other matplotlib parameters for :func:`matplotlib.pyplot.figure` to
@@ -1122,7 +1237,10 @@ class FigStore( object ):
1122
1237
  fig.close()
1123
1238
 
1124
1239
  As in the example above, the most convenient way to create a ``FigStore`` object is
1125
- to call `:meth:`cdxcore.dynaplot.DynaFig.store` on your figure.
1240
+ to call :meth:`cdxcore.dynaplot.DynaFig.store` on your figure.
1241
+
1242
+ The above pattern is not speed or memory optimal. It is more efficient to modify the `artist <https://matplotlib.org/stable/tutorials/artists.html>`__
1243
+ directly. While true, for most applications a somewhat rude cancel+replace is simpler. ``FigStore`` was introduced to facilitiate that.
1126
1244
  """
1127
1245
 
1128
1246
  def __init__(self):
@@ -1132,7 +1250,7 @@ class FigStore( object ):
1132
1250
  def add(self, element : Artist):
1133
1251
  """
1134
1252
  Add an element to the store.
1135
- The same operation is available using +=
1253
+ The same operation is available using ``+=``.
1136
1254
 
1137
1255
  Parameters
1138
1256
  ----------
@@ -1142,8 +1260,8 @@ class FigStore( object ):
1142
1260
 
1143
1261
  Returns
1144
1262
  -------
1145
- ``self`` : ``Figstore``
1146
- This way compound statements ``a.add(x).add(y).add(z)`` work.
1263
+ self : ``Figstore``
1264
+ Returns ``self``. This way compound statements ``a.add(x).add(y).add(z)`` work.
1147
1265
  """
1148
1266
  if element is None:
1149
1267
  return self
@@ -1154,7 +1272,7 @@ class FigStore( object ):
1154
1272
  self._elements.append( element )
1155
1273
  return self
1156
1274
  if not isinstance(element,Collection):
1157
- raise ValueError("Cannot add element of type '{type(element).__name__}' as it is not derived from matplotlib.artist.Artist, nor is it a Collection")
1275
+ raise ValueError(f"Cannot add element of type '{type(element).__name__}' as it is not derived from matplotlib.artist.Artist, nor is it a Collection")
1158
1276
  for l in element:
1159
1277
  self += l
1160
1278
  return self
@@ -1182,12 +1300,12 @@ class FigStore( object ):
1182
1300
  rem( e.deferred_result )
1183
1301
  return
1184
1302
  if not e is None:
1185
- raise RuntimeError("Cannot remove() element of type '{type(e).__name__}' as it is not derived from matplotlib.artist.Artist, nor is it a Collection")
1303
+ raise RuntimeError(f"Cannot remove() element of type '{type(e).__name__}' as it is not derived from matplotlib.artist.Artist, nor is it a Collection")
1186
1304
 
1187
1305
  while len(self._elements) > 0:
1188
1306
  rem( self._elements.pop(0) )
1189
1307
  self._elements = []
1190
- gc.collect()
1308
+ #gc.collect()
1191
1309
 
1192
1310
  clear = remove
1193
1311
 
@@ -1307,6 +1425,6 @@ def colors_tableau():
1307
1425
  return colors("tableau")
1308
1426
 
1309
1427
  def colors_xkcd():
1310
- """ Iterator for `xkcd <https://xkcd.com/color/rgb/>`__. matplotlib colors """
1428
+ """ Iterator for `xkcd <https://xkcd.com/color/rgb/>`__ matplotlib colors """
1311
1429
  return colors("xkcd")
1312
1430