cdxcore 0.1.31__py3-none-any.whl → 0.1.33__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")
222
-
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")
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")
226
270
 
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()
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")
231
274
 
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 :meth:`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 :meth:`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
@@ -458,7 +516,7 @@ class DynaAx(_DynaDeferred):
458
516
  Parameters
459
517
  ----------
460
518
  args, scalex, scaley, data, kwargs : ...
461
- See :func:`matplotlib.axes.plot`.
519
+ See :meth:`matplotlib.axes.plot`.
462
520
 
463
521
  Returns
464
522
  -------
@@ -568,53 +626,59 @@ class _DynaGridSpec(_DynaDeferred):
568
626
 
569
627
  class DynaFig(_DynaDeferred):
570
628
  """
571
- Deferred wrapper around :class:`matplotlib.pyplot.figure`.
629
+ Deferred wrapper around :class:`matplotlib.figure.Figure`.
572
630
 
573
- Wraps matplotlib :class:`matplotlib.pyplot.figure`. Provides a simple :meth:`cdxcore.dynaplot.DynaFig.add_subplot` without the need to pre-specify axes positions
631
+ Provides a simple :meth:`cdxcore.dynaplot.DynaFig.add_subplot` without the need to pre-specify axes positions
574
632
  as is common for :mod:`matplotlib`.
575
633
 
576
- See :func:`cdxcore.dynaplot.figure` for more information.
634
+ Construct elements of this class with :func:`cdxcore.dynaplot.figure`.
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
610
-
611
- verify( not 'cols' in fig_kwargs, "Unknown keyword 'cols'. Did you mean 'columns'?", exception=ValueError)
612
-
613
648
  if not fig_size is None:
614
649
  verify( not 'figsize' in fig_kwargs, "Cannot specify both `figsize` and `fig_size`", exception=ValueError)
615
650
  fig_kwargs['figsize'] = fig_size
651
+
652
+ self._hdisplay = None
653
+ self._axes = [] #:
654
+ #:
655
+ self._grid_specs = []
656
+ self._fig = None
657
+ self._row_size = int(row_size)
658
+ self._col_size = int(col_size)
659
+ self._cols = int(columns)
660
+ self._tight = bool(tight)
661
+ self._tight_para = None
662
+ self._fig_kwargs = dict(fig_kwargs)
663
+ if self._tight:
664
+ self._fig_kwargs['tight_layout'] = True
665
+ verify( self._row_size > 0 and self._col_size > 0 and self._cols > 0, "Invalid input.", exception=ValueError)
666
+ self._this_row = 0
667
+ self._this_col = 0
668
+ self._max_col = 0
669
+ self._fig_title = title
670
+ self._closed = False
671
+ self._enter_count = 0
672
+ self.draw_mode = draw_mode
673
+ #: A combination of :class:`cdxcore.dynaplot.MODE` flags on how to draw plots
674
+ #: once they were rendered. The required function call differs by IPython platform.
675
+ #: The default, :attr:`cdxcore.dynaplot.MODE.DEFAULT` draws well on Jupyter notebooks
676
+ #: and VSCode if :func:`cdxcore.dynaplot.dynamic_backend` was called
677
+ #: (which sets ``%matplotlib inline``).
678
+
679
+ verify( not 'cols' in fig_kwargs, "Unknown keyword 'cols'. Did you mean 'columns'?", exception=ValueError)
616
680
 
617
- dyna_title = title if len(title) <= 20 else ( title[:17] + "..." ) if not title is None else None
681
+ dyna_title = ( title if len(title) <= 20 else ( title[:17] + "..." ) ) if not title is None else None
618
682
  _DynaDeferred.__init__(self, f"figure('{dyna_title}')" if not title is None else "figure()" )
619
683
 
620
684
  def __str__(self):
@@ -624,10 +688,45 @@ class DynaFig(_DynaDeferred):
624
688
  """ Ensure the figure is closed """
625
689
  self.close()
626
690
 
627
- def add_subplot(self, title : str = None, *,
628
- new_row : bool = None,
629
- spec_pos : type = None,
630
- projection: str = None,
691
+ # properties
692
+ # ----------
693
+
694
+ @property
695
+ def axes(self) -> list[DynaAx]:
696
+ """
697
+ List of axes. Until :meth:`cdxcore.dynaplot.DynaFig.render` is called, these are :class:`cdxcore.dynaplot.DynaAx` objects;
698
+ afterwards, these are :class:`matplotlib.axes.Axes` objects.
699
+ """
700
+ return self._axes
701
+
702
+ @property
703
+ def fig(self) -> plt.Figure|None:
704
+ """
705
+ Returns the figure or ``None`` if it was not yet rendered.
706
+ """
707
+ return self._fig
708
+
709
+ @property
710
+ def hdisplay(self) -> display.DisplayHandle|None:
711
+ """
712
+ Returns the :class:`IPython.display.DisplayHandle` for the current display, if
713
+ ``MODE.HDISPLAY`` was used for ``draw_mode`` when the figure was constructed, and if the figure
714
+ was rendered yet. Otherwise returns ``None``.
715
+ """
716
+ return self._hdisplay
717
+
718
+ @property
719
+ def is_closed(self) -> bool:
720
+ """ Returrns whether the figure was closed """
721
+ return self._closed
722
+
723
+ # functionality
724
+ # -------------
725
+
726
+ def add_subplot(self, title : str|None = None, *,
727
+ new_row : bool|None = None,
728
+ spec_pos : type|None = None,
729
+ projection: str|None = None,
631
730
  **kwargs) -> DynaAx:
632
731
  """
633
732
  Adds a subplot.
@@ -640,29 +739,35 @@ class DynaFig(_DynaDeferred):
640
739
 
641
740
  This function returns a wrapper which defers the creation of the actual sub plot until
642
741
  :meth:`cdxcore.dynaplot.DynaFig.render` or :meth:`cdxcore.dynaplot.DynaFig.close` is called.
742
+
643
743
  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.
744
+ is set. Use :meth:`cdxcore.dynaplot.DynaFig.add_axes` to draw plots at any time/.
645
745
 
646
746
  Parameters
647
747
  ----------
648
- title : str, optional
748
+ title : str | None, default ``None``
649
749
  Optional title for the plot.
650
750
 
651
- new_row : bool, optional
751
+ new_row : bool | None, default ``None``
652
752
  Whether to force a new row and place this polt in the first column. Default is ``False``.
653
753
 
654
- spec_pos : optional
655
- Grid spec position, or ``None``.
754
+ spec_pos : grid spec | None, default ``None``
755
+ Grid spec position from :meth:`cdxcore.dynaplot.DynaFig.add_gridspec`, or ``None``.
656
756
 
657
- projection : str, optional
757
+ projection : str | None, default ``None``
658
758
  What ``projection`` to use. The default ``None`` matches the default choice for
659
759
  :meth:`matplotlib.figure.Figure.add_subplot`.
660
760
 
661
761
  kwargs : dict
662
762
  other arguments to be passed to matplotlib's :meth:`matplotlib.figure.Figure.add_subplot`.
763
+
764
+ Returns
765
+ -------
766
+ Axis : :class:`cdxcore.dynaplot.DynaAx`
767
+ A wrapper around an matplotlib axis.
663
768
  """
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")
769
+ verify( not self._closed, "Cannot call add_subplot() after close() was called")
770
+ verify( self._fig is None, "Cannot call add_subplot() after render() was called. Use add_axes() instead")
666
771
 
667
772
  # backward compatibility:
668
773
  # previous versions has "new_row" first.
@@ -672,7 +777,7 @@ class DynaFig(_DynaDeferred):
672
777
  if not spec_pos is None:
673
778
  assert new_row is None, ("Cannot specify 'new_row' when 'spec_pos' is specified")
674
779
  ax = DynaAx( fig_id=hash(self),
675
- fig_list=self.axes,
780
+ fig_list=self._axes,
676
781
  row=None,
677
782
  col=None,
678
783
  title=title,
@@ -683,30 +788,49 @@ class DynaFig(_DynaDeferred):
683
788
 
684
789
  else:
685
790
  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
791
+ if (self._this_col >= self._cols) or ( new_row and not self._this_col == 0 ):
792
+ self._this_col = 0
793
+ self._this_row = self._this_row + 1
794
+ if self._max_col < self._this_col:
795
+ self._max_col = self._this_col
691
796
  ax = DynaAx( fig_id=hash(self),
692
- fig_list=self.axes,
693
- row=self.this_row,
694
- col=self.this_col,
797
+ fig_list=self._axes,
798
+ row=self._this_row,
799
+ col=self._this_col,
695
800
  spec_pos=None,
696
801
  rect=None,
697
802
  title=title,
698
803
  projection=projection,
699
804
  kwargs=dict(kwargs) )
700
- self.this_col += 1
701
- assert ax in self.axes
805
+ self._this_col += 1
806
+ assert ax in self._axes
702
807
  return ax
703
808
 
704
809
  add_plot = add_subplot
810
+
811
+ def add_subplots( self, titles : list[str]|int ) -> tuple[DynaAx]:
812
+ """
813
+ Generate a number of sub-plots in one function call.
814
+
815
+ Parameters
816
+ ----------
817
+ titles : list[str] | int
818
+ Either a list of plot titles, or a number.
819
+
820
+ Returns
821
+ -------
822
+ Sub-plots: tuple
823
+ A tuple of sub-plots, one for each ``title``.
824
+ """
825
+ if isinstance( titles, int ):
826
+ return tuple( self.add_subplot() for _ in range(titles) )
827
+ else:
828
+ return tuple( self.add_subplot(title) for title in titles )
705
829
 
706
830
  def add_axes( self,
707
831
  rect : tuple,
708
832
  title : str|None = None, *,
709
- projection: str = None,
833
+ projection: str|None = None,
710
834
  **kwargs ) -> DynaAx:
711
835
  """
712
836
  Add a freely placed sub plot.
@@ -726,22 +850,31 @@ class DynaFig(_DynaDeferred):
726
850
  The dimensions (left, bottom, width, height) of the new plot.
727
851
  All quantities are in fractions of figure width and height.
728
852
 
729
- title : str, optional
853
+ title : str | None, default ``None``
730
854
  Title for the plot, or ``None`` for no plot.
731
855
 
732
- projection : str, optional
856
+ projection : str | None, default ``None``
733
857
  What ``projection`` to use. The default ``None`` matches the default choice for
734
858
  :meth:`matplotlib.figure.Figure.add_axes`
735
-
859
+
736
860
  args, kwargs :
737
861
  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
862
 
863
+ Returns
864
+ -------
865
+ Axis : :class:`cdxcore.dynaplot.DynaAx`
866
+ A wrapper around an matplotlib axis.
867
+ """
868
+ verify( not self._closed, "Cannot call add_subplot() after close() was called")
869
+ verify( not isinstance(rect,str), "'rect' is a string ... did you mix up the order of 'title' and 'rect'?")
741
870
  title = str(title) if not title is None else None
871
+ if not isinstance(rect, tuple):
872
+ rect = tuple(rect)
873
+ verify( len(rect)==4, lambda:f"'rect' must be a tuple of length 4. Found '{rect}'")
874
+ verify( np.isfinite(rect).all(), lambda:f"'rect' has infinite elements: {rect}")
742
875
 
743
876
  ax = DynaAx( fig_id=hash(self),
744
- fig_list=self.axes,
877
+ fig_list=self._axes,
745
878
  row=None,
746
879
  col=None,
747
880
  title=title,
@@ -749,17 +882,17 @@ class DynaFig(_DynaDeferred):
749
882
  rect=rect,
750
883
  projection=projection,
751
884
  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 )
885
+ assert ax in self._axes
886
+ if not self._fig is None:
887
+ ax._initialize( self._fig, rows=None, cols=None )
755
888
  return ax
756
889
 
757
890
  def add_gridspec(self, ncols=1, nrows=1, **kwargs):
758
891
  """
759
892
  Wrapper for :meth:`matplotlib.figure.Figure.add_gridspec`, returning a defered ``GridSpec``.
760
893
  """
761
- grid = _DynaGridSpec( ncols=ncols, nrows=nrows, cnt=len(self.grid_specs), kwargs=kwargs )
762
- self.grid_specs.append( grid )
894
+ grid = _DynaGridSpec( ncols=ncols, nrows=nrows, cnt=len(self._grid_specs), kwargs=kwargs )
895
+ self._grid_specs.append( grid )
763
896
  return grid
764
897
 
765
898
  def next_row(self):
@@ -769,11 +902,11 @@ class DynaFig(_DynaDeferred):
769
902
  The next plot generated by :meth:`cdxcore.dynaplot.DynaFig.add_subplot` will
770
903
  appears in the first column of the next row.
771
904
  """
772
- verify( self.fig is None, "Cannot call next_row() after render() was called")
773
- if self.this_col == 0:
905
+ verify( self._fig is None, "Cannot call next_row() after render() was called")
906
+ if self._this_col == 0:
774
907
  return
775
- self.this_col = 0
776
- self.this_row = self.this_row + 1
908
+ self._this_col = 0
909
+ self._this_row = self._this_row + 1
777
910
 
778
911
  def render(self, draw : bool = True ):
779
912
  """
@@ -786,47 +919,47 @@ class DynaFig(_DynaDeferred):
786
919
 
787
920
  Parameters
788
921
  ----------
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`.
922
+ draw : bool, default ``True``
923
+ If False, then the figure is created, but not drawn.
924
+ You usually use ``False`` when planning to use
925
+ :func:`cdxcore.dynaplot.DynaFig.savefig` or :func:`cdxcore.dynaplot.DynaFig.to_bytes`.
793
926
  """
794
- verify( not self.closed, "Cannot call render() after close() was called")
795
- if len(self.axes) == 0:
927
+ verify( not self._closed, "Cannot call render() after close() was called")
928
+ if len(self._axes) == 0:
796
929
  return
797
- if self.fig is None:
930
+ if self._fig is None:
798
931
  # 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 )
932
+ if not 'figsize' in self._fig_kwargs:
933
+ self._fig_kwargs['figsize'] = ( self._col_size*(self._max_col+1), self._row_size*(self._this_row+1))
934
+ self._fig = plt.figure( **self._fig_kwargs )
935
+ if self._tight:
936
+ self._fig.tight_layout()
937
+ self._fig.set_tight_layout(True)
938
+ if not self._fig_title is None:
939
+ self._fig.suptitle( self._fig_title )
807
940
  # create all grid specs
808
- for gs in self.grid_specs:
809
- gs._initialize( self.fig )
941
+ for gs in self._grid_specs:
942
+ gs._initialize( self._fig )
810
943
  # create all axes
811
- for ax in self.axes:
812
- ax._initialize( self.fig, rows=self.this_row+1, cols=self.max_col+1 )
944
+ for ax in self._axes:
945
+ ax._initialize( self._fig, rows=self._this_row+1, cols=self._max_col+1 )
813
946
  # execute all deferred calls to fig()
814
- self.deferred_resolve( self.fig )
947
+ self.deferred_resolve( self._fig )
815
948
 
816
949
  if not draw:
817
950
  return
818
951
  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)
952
+ if self._hdisplay is None:
953
+ self._hdisplay = display.display(display_id=True)
954
+ 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")
955
+ self._hdisplay.update(self._fig)
823
956
  if self.draw_mode & MODE.CANVAS_IDLE:
824
- self.fig.canvas.draw_idle()
957
+ self._fig.canvas.draw_idle()
825
958
  if self.draw_mode & MODE.CANVAS_DRAW:
826
- self.fig.canvas.draw()
959
+ self._fig.canvas.draw()
827
960
  if self.draw_mode & MODE.PLT_SHOW:
828
961
  plt.show()
829
- gc.collect() # for some unknown reason this is required in VSCode
962
+ #gc.collect() # for some unknown reason this is required in VSCode
830
963
 
831
964
  def savefig(self, fname : str,
832
965
  silent_close : bool = True,
@@ -834,26 +967,27 @@ class DynaFig(_DynaDeferred):
834
967
  """
835
968
  Saves the figure to a file.
836
969
 
837
- Wrapper around :func:`matplotlib.pyplot.savefig`. Essentially, this function writes the figure to a file]
970
+ Wrapper around :func:`matplotlib.pyplot.savefig`. Essentially, `this function <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html>`__
971
+ writes the figure to a file
838
972
  rather than displaying itl.
839
973
 
840
974
  Parameters
841
975
  ----------
842
- fname : str
843
- `filename or file-like object <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html>`__
976
+ fname : str
977
+ `filename or file-like object <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html>`__
844
978
 
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`.
979
+ silent_close : bool, default ``True``
980
+ If ``True`` (the default), call :meth:`cdxcore.dynaplot.DynaFig.close` once the figure was saved to disk.
981
+ Unless the figure was drawn before, this means that the figure will not be displayed in jupyter, and
982
+ subsequent activity is blocked.
983
+
984
+ kwargs : dict
985
+ These arguments will be passed to :meth:`matplotlib.pyplot.savefig`.
852
986
  """
853
- verify( not self.closed, "Cannot call savefig() after close() was called")
854
- if self.fig is None:
987
+ verify( not self._closed, "Cannot call savefig() after close() was called")
988
+ if self._fig is None:
855
989
  self.render(draw=False)
856
- self.fig.savefig( fname, **kwargs )
990
+ self._fig.savefig( fname, **kwargs )
857
991
  if silent_close:
858
992
  self.close(render=False)
859
993
 
@@ -870,21 +1004,21 @@ class DynaFig(_DynaDeferred):
870
1004
 
871
1005
  Parameters
872
1006
  ----------
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.
1007
+ silent_close : bool, default ``True``
1008
+ If ``True``, call :meth:`cdxcore.dynaplot.DynaFig.close` after this genersating the byte streanm.
1009
+ Unless the figure was drawn before, this means that the figure will not be displayed in jupyter, and
1010
+ subsequent activity is blocked.
877
1011
 
878
1012
  Returns
879
1013
  -------
880
- image : bytes
881
- Buyte stream of the image.
1014
+ image : bytes
1015
+ Buyte stream of the image.
882
1016
  """
883
- verify( not self.closed, "Cannot call savefig() after close() was called")
1017
+ verify( not self._closed, "Cannot call savefig() after close() was called")
884
1018
  img_buf = io.BytesIO()
885
- if self.fig is None:
1019
+ if self._fig is None:
886
1020
  self.render(draw=False)
887
- self.fig.savefig( img_buf )
1021
+ self._fig.savefig( img_buf )
888
1022
  if silent_close:
889
1023
  self.close(render=False)
890
1024
  data = img_buf.getvalue()
@@ -893,7 +1027,8 @@ class DynaFig(_DynaDeferred):
893
1027
 
894
1028
  @staticmethod
895
1029
  def store():
896
- """ Create a FigStore(). Such a store allows managing graphical elements (artists) dynamically. """
1030
+ """ Create a :class:`cdxcore.dynaplot.FigStore`. Such a store allows managing graphical elements (artists) dynamically. See the examples
1031
+ in the introduction. """
897
1032
  return FigStore()
898
1033
 
899
1034
  def close(self, render : bool = True,
@@ -902,55 +1037,50 @@ class DynaFig(_DynaDeferred):
902
1037
  Closes the figure.
903
1038
 
904
1039
  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.
1040
+ By dault this function will call :meth:`cdxcore.dynaplot.DynaFig.render` to draw the figure, and then close it.
906
1041
 
907
1042
  Parameters
908
1043
  ----------
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 :
1044
+ render : bool, default ``True``
1045
+ If ``True``, the default, this function will call :meth:`cdxcore.dynaplot.DynaFig.render` and therefore renders the figure before closing the figure.
1046
+ clear : bool, default ``False``
912
1047
  If ``True``, all axes will be cleared. *This is experimental.* The default is ``False``.
913
1048
  """
914
- if not self.closed:
1049
+ if not self._closed:
915
1050
  # magic wand to avoid printing an empty figure message
916
1051
  if clear:
917
- if not self.fig is None:
1052
+ if not self._fig is None:
918
1053
  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 )
1054
+ return type(self)._repr_html_(self) if len(self._axes) > 0 else "</HTML>"
1055
+ self._fig._repr_html_ = types.MethodType(repr_magic,self._fig)
1056
+ self.delaxes( self._axes, render=render )
922
1057
  elif render:
923
1058
  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
1059
+ if not self._fig is None:
1060
+ plt.close(self._fig)
1061
+ self._fig = None
1062
+ self._closed = True
1063
+ self._hdisplay = None
929
1064
  gc.collect()
930
1065
 
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
1066
  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()
1067
+ """ Calls :meth:`cdxcore.dynaplot.DynaAx.remove` for all :attr:`cdxcore.dynaplot.DynaFig.axes` """
1068
+ while len(self._axes) > 0:
1069
+ self._axes[0].remove()
940
1070
  if render:
941
1071
  self.render(draw=True)
942
1072
 
943
1073
  def delaxes( self, ax : DynaAx, *, render : bool = False ):
944
1074
  """
945
- Equivalent of :func:`matplotlib.figure.Figure.delaxes`, but this function can also take a list.
1075
+ Equivalent of :meth:`matplotlib.figure.Figure.delaxes`, but this function can also take a list.
946
1076
  """
947
- verify( not self.closed, "Cannot call render() after close() was called")
1077
+ verify( not self._closed, "Cannot call render() after close() was called")
948
1078
  if isinstance( ax, Collection ):
949
1079
  ax = list(ax)
950
1080
  for x in ax:
951
1081
  x.remove()
952
1082
  else:
953
- assert ax in self.axes, ("Cannot delete axes which wasn't created by this figure")
1083
+ assert ax in self._axes, ("Cannot delete axes which wasn't created by this figure")
954
1084
  ax.remove()
955
1085
  if render:
956
1086
  self.render()
@@ -959,24 +1089,28 @@ class DynaFig(_DynaDeferred):
959
1089
  # -----------------------
960
1090
 
961
1091
  def __enter__(self):
1092
+ self._enter_count += 1
962
1093
  return self
963
1094
 
964
1095
  def __exit__(self, *args, **kwargs):
965
- self.close()
1096
+ self._enter_count -= 1
1097
+ if self._enter_count <= 0:
1098
+ self.close()
966
1099
  return False
967
1100
 
968
- def figure( title : str = None, *,
1101
+ def figure( title : str|None = None, *,
969
1102
  row_size : int = 5,
970
1103
  col_size : int = 4,
971
- fig_size : tuple[int] = None,
1104
+ fig_size : tuple[int]|None = None,
972
1105
  columns : int = 5,
973
1106
  tight : bool = True,
974
- draw_mode: int = MODE.JUPYTER,
1107
+ draw_mode: int = MODE.DEFAULT,
1108
+ add_plots: int|list|None = None,
975
1109
  **fig_kwargs ):
976
1110
  """
977
1111
  Creates a dynamic figure of type :class:`cdxcore.dynaplot.DynaFig`.
978
1112
 
979
- By default the ``fig_size`` of the underlying :class:`matplotlib.pyplot.figure`
1113
+ By default the ``fig_size`` of the underlying :class:`matplotlib.figure.Figure`
980
1114
  will be derived from the number of plots vs ``cols``, ``row_size`` and ``col_size``
981
1115
  as ``(col_size* (N%col_num), row_size (N//col_num))``.
982
1116
 
@@ -988,7 +1122,8 @@ def figure( title : str = None, *,
988
1122
 
989
1123
  2) Call :meth:`cdxcore.dynaplot.DynaFig.render` to place those plots.
990
1124
 
991
- 3) Call :meth:`cdxcore.dynaplot.DynaFig.close` to close the figure and avoid duplicate copies in Jupyter.
1125
+ 3) Call :meth:`cdxcore.dynaplot.DynaFig.close` to close the figure and avoid duplicate copies in Jupyter; a convient wrapper is to use
1126
+ ``with`` to ensure ``close()`` gets called. This pattern is used below.
992
1127
 
993
1128
  **Examples:**
994
1129
 
@@ -996,23 +1131,21 @@ def figure( title : str = None, *,
996
1131
  matplotlib need to pre-specify axes positions::
997
1132
 
998
1133
  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()
1134
+ with dynaplot.figure("Two plots") as fig:
1135
+ ax = fig.add_subplot("1")
1136
+ ax.plot(x,y)
1137
+ ax = fig.add_subplot("2")
1138
+ ax.plot(x,y)
1005
1139
 
1006
1140
  Here is an example using :meth:`matplotlib.figure.Figure.add_gridspec`::
1007
1141
 
1008
1142
  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()
1143
+ with dynaplot.figure() as fig:
1144
+ gs = fig.add_gridspec(2,2)
1145
+ ax = fig.add_subplot( gs[:,0] )
1146
+ ax.plot(x,y)
1147
+ ax = fig.add_subplot( gs[:,1] )
1148
+ ax.plot(x,y)
1016
1149
 
1017
1150
  **Important Functions:**
1018
1151
 
@@ -1020,6 +1153,8 @@ def figure( title : str = None, *,
1020
1153
  defer all other function calls to the figure
1021
1154
  object until :meth:`cdxcore.dynaplot.DynaFig.render`
1022
1155
  or :meth:`cdxcore.dynaplot.DynaFig.close` are called.
1156
+ Whenever you use ``with`` at the end of the context window :meth:`cdxcore.dynaplot.DynaFig.close`
1157
+ will be called for you.
1023
1158
 
1024
1159
  The following direct members are important for using the framework:
1025
1160
 
@@ -1040,39 +1175,43 @@ def figure( title : str = None, *,
1040
1175
 
1041
1176
  Parameters
1042
1177
  ----------
1043
- title : str, optional
1178
+ title : str, default ``None``
1044
1179
  An optional title which will be passed to :meth:`matplotlib.pyplot.suptitle`.
1045
1180
 
1046
- fig_size : tuple[int], optional
1047
- By default the ``fig_size`` of the underlying :class:`matplotlib.pyplot.figure`
1181
+ fig_size : tuple[int] | None, default ``None``
1182
+ By default the ``fig_size`` of the underlying :func:`matplotlib.pyplot.figure`
1048
1183
  will be derived from the number of plots vs ``cols``, ``row_size`` and ``col_size``
1049
1184
  as ``(col_size* (N%col_num), row_size (N//col_num))``.
1050
1185
 
1051
1186
  If ``fig_size`` is specified then ``row_size`` and ``col_size`` are ignored.
1052
1187
 
1053
- row_size : int, optional
1188
+ row_size : int, default ``5``
1054
1189
  Size for a row for matplot lib. Default is 5.
1055
1190
  This is ignored if ``fig_size`` is specified.
1056
1191
 
1057
- col_size : int, optional
1192
+ col_size : int, default ``4``
1058
1193
  Size for a column for matplot lib. Default is 4.
1059
1194
  This is ignored if ``fig_size`` is specified.
1060
1195
 
1061
- columns : int, optional
1196
+ columns : int, default ``5``
1062
1197
  How many columns to use when :meth:`cdxcore.dynaplot.DynaFig.add_subplot` is used.
1063
1198
  If omitted then the default is 5.
1064
1199
 
1065
- tight : bool, optional
1200
+ tight : bool, default ``True``
1066
1201
  Short cut for :meth:`matplotlib.figure.Figure.set_tight_layout`. The default is ``True``.
1067
1202
 
1068
1203
  Note that when ``tight`` is ``True`` and :meth:`cdxcore.dynaplot.DynaFig.add_axes`
1069
1204
  is called a :class:`UserWarning` is generated. Turn ``tight`` off to avoid this.
1070
-
1071
- draw_mode : int, optional
1205
+
1206
+ draw_mode : int, default ``MODE.DEFAULT``
1072
1207
  A combination of :class:`cdxcore.dynaplot.MODE` flags on how to draw plots
1073
1208
  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`.
1209
+ The default, :attr:`cdxcore.dynaplot.MODE.DEFAULT` draws well on Jupyter notebooks
1210
+ and in VSCode if ``%matplotlin inline`` or ``widget`` is used. The latter requires the
1211
+ packages ``ipympl`` and ``ipywidgets``.
1212
+
1213
+ Use the function :func:`cdxcore.dynaplot.dynamic_backend` to
1214
+ set the ``widget`` mode if possible.
1076
1215
 
1077
1216
  fig_kwargs :
1078
1217
  Other matplotlib parameters for :func:`matplotlib.pyplot.figure` to
@@ -1084,7 +1223,7 @@ def figure( title : str = None, *,
1084
1223
  figure: :class:`cdxcore.dynaplot.DynaFig`
1085
1224
  A dynamic figure.
1086
1225
  """
1087
- return DynaFig( title=title,
1226
+ fig = DynaFig( title=title,
1088
1227
  row_size=row_size,
1089
1228
  col_size=col_size,
1090
1229
  fig_size=fig_size,
@@ -1093,6 +1232,7 @@ def figure( title : str = None, *,
1093
1232
  draw_mode=draw_mode,
1094
1233
  **fig_kwargs
1095
1234
  )
1235
+ return fig
1096
1236
 
1097
1237
  # ----------------------------------------------------------------------------------
1098
1238
  # Utility class for animated content
@@ -1122,7 +1262,10 @@ class FigStore( object ):
1122
1262
  fig.close()
1123
1263
 
1124
1264
  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.
1265
+ to call :meth:`cdxcore.dynaplot.DynaFig.store` on your figure.
1266
+
1267
+ The above pattern is not speed or memory optimal. It is more efficient to modify the `artist <https://matplotlib.org/stable/tutorials/artists.html>`__
1268
+ directly. While true, for most applications a somewhat rude cancel+replace is simpler. ``FigStore`` was introduced to facilitiate that.
1126
1269
  """
1127
1270
 
1128
1271
  def __init__(self):
@@ -1132,7 +1275,7 @@ class FigStore( object ):
1132
1275
  def add(self, element : Artist):
1133
1276
  """
1134
1277
  Add an element to the store.
1135
- The same operation is available using +=
1278
+ The same operation is available using ``+=``.
1136
1279
 
1137
1280
  Parameters
1138
1281
  ----------
@@ -1142,8 +1285,8 @@ class FigStore( object ):
1142
1285
 
1143
1286
  Returns
1144
1287
  -------
1145
- ``self`` : ``Figstore``
1146
- This way compound statements ``a.add(x).add(y).add(z)`` work.
1288
+ self : ``Figstore``
1289
+ Returns ``self``. This way compound statements ``a.add(x).add(y).add(z)`` work.
1147
1290
  """
1148
1291
  if element is None:
1149
1292
  return self
@@ -1154,7 +1297,7 @@ class FigStore( object ):
1154
1297
  self._elements.append( element )
1155
1298
  return self
1156
1299
  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")
1300
+ 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
1301
  for l in element:
1159
1302
  self += l
1160
1303
  return self
@@ -1182,12 +1325,12 @@ class FigStore( object ):
1182
1325
  rem( e.deferred_result )
1183
1326
  return
1184
1327
  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")
1328
+ 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
1329
 
1187
1330
  while len(self._elements) > 0:
1188
1331
  rem( self._elements.pop(0) )
1189
1332
  self._elements = []
1190
- gc.collect()
1333
+ #gc.collect()
1191
1334
 
1192
1335
  clear = remove
1193
1336
 
@@ -1251,8 +1394,8 @@ def color(i : int, table : str ="css4"):
1251
1394
  i : int
1252
1395
  Integer number. Colors will be rotated.
1253
1396
 
1254
- table : str, optional
1255
- Which color table from :func:`matplotlib.colors` to use: `"css4"`, `"base"`, `"tableau"` or `"xkcd"`.
1397
+ table : str, default ``css4``
1398
+ Which `color table from matplotlib <https://matplotlib.org/stable/users/explain/colors/index.html>`__ to use: `"css4"`, `"base"`, `"tableau"` or `"xkcd"`.
1256
1399
  Default is ``"css4"``.
1257
1400
 
1258
1401
  Returns
@@ -1307,6 +1450,6 @@ def colors_tableau():
1307
1450
  return colors("tableau")
1308
1451
 
1309
1452
  def colors_xkcd():
1310
- """ Iterator for `xkcd <https://xkcd.com/color/rgb/>`__. matplotlib colors """
1453
+ """ Iterator for `xkcd <https://xkcd.com/color/rgb/>`__ matplotlib colors """
1311
1454
  return colors("xkcd")
1312
1455