cdxcore 0.1.31__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/__init__.py +1 -1
- cdxcore/dynaplot.py +394 -276
- cdxcore/uniquehash.py +1 -1
- {cdxcore-0.1.31.dist-info → cdxcore-0.1.32.dist-info}/METADATA +1 -1
- {cdxcore-0.1.31.dist-info → cdxcore-0.1.32.dist-info}/RECORD +8 -8
- {cdxcore-0.1.31.dist-info → cdxcore-0.1.32.dist-info}/WHEEL +0 -0
- {cdxcore-0.1.31.dist-info → cdxcore-0.1.32.dist-info}/licenses/LICENSE +0 -0
- {cdxcore-0.1.31.dist-info → cdxcore-0.1.32.dist-info}/top_level.txt +0 -0
cdxcore/__init__.py
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
ref_ax = None
|
|
187
|
+
import time
|
|
188
|
+
from cdxcore.dynaplot import figure
|
|
189
|
+
|
|
152
190
|
x = np.linspace(0,1,100)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
ax.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
300
|
-
""" Setting which works for Jupyter lab as far as we can tell. """
|
|
301
|
-
|
|
302
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
643
|
+
draw_mode: int = MODE.DEFAULT,
|
|
586
644
|
**fig_kwargs ):
|
|
587
645
|
"""
|
|
588
|
-
|
|
646
|
+
__init__
|
|
589
647
|
"""
|
|
590
|
-
|
|
591
|
-
self.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
self.
|
|
595
|
-
self.
|
|
596
|
-
self.
|
|
597
|
-
self.
|
|
598
|
-
self.
|
|
599
|
-
self.
|
|
600
|
-
self.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
self.
|
|
605
|
-
self.
|
|
606
|
-
self.
|
|
607
|
-
self.
|
|
608
|
-
self.
|
|
609
|
-
self.
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
|
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,
|
|
749
|
+
title : str | None, default ``None``
|
|
649
750
|
Optional title for the plot.
|
|
650
751
|
|
|
651
|
-
new_row : bool,
|
|
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 :
|
|
655
|
-
Grid spec position
|
|
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,
|
|
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.
|
|
665
|
-
verify( self.
|
|
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.
|
|
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.
|
|
687
|
-
self.
|
|
688
|
-
self.
|
|
689
|
-
if self.
|
|
690
|
-
self.
|
|
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.
|
|
693
|
-
row=self.
|
|
694
|
-
col=self.
|
|
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.
|
|
701
|
-
assert ax in self.
|
|
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,
|
|
835
|
+
title : str | None, default ``None``
|
|
730
836
|
Title for the plot, or ``None`` for no plot.
|
|
731
837
|
|
|
732
|
-
projection : str,
|
|
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.
|
|
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.
|
|
753
|
-
if not self.
|
|
754
|
-
ax._initialize( self.
|
|
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.
|
|
762
|
-
self.
|
|
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.
|
|
773
|
-
if self.
|
|
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.
|
|
776
|
-
self.
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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.
|
|
795
|
-
if len(self.
|
|
909
|
+
verify( not self._closed, "Cannot call render() after close() was called")
|
|
910
|
+
if len(self._axes) == 0:
|
|
796
911
|
return
|
|
797
|
-
if self.
|
|
912
|
+
if self._fig is None:
|
|
798
913
|
# create figure
|
|
799
|
-
if not 'figsize' in self.
|
|
800
|
-
self.
|
|
801
|
-
self.
|
|
802
|
-
if self.
|
|
803
|
-
self.
|
|
804
|
-
self.
|
|
805
|
-
if not self.
|
|
806
|
-
self.
|
|
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.
|
|
809
|
-
gs._initialize( self.
|
|
923
|
+
for gs in self._grid_specs:
|
|
924
|
+
gs._initialize( self._fig )
|
|
810
925
|
# create all axes
|
|
811
|
-
for ax in self.
|
|
812
|
-
ax._initialize( self.
|
|
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.
|
|
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.
|
|
820
|
-
self.
|
|
821
|
-
verify( not self.
|
|
822
|
-
self.
|
|
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.
|
|
939
|
+
self._fig.canvas.draw_idle()
|
|
825
940
|
if self.draw_mode & MODE.CANVAS_DRAW:
|
|
826
|
-
self.
|
|
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
|
-
|
|
843
|
-
|
|
957
|
+
fname : str
|
|
958
|
+
`filename or file-like object <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html>`__
|
|
844
959
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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.
|
|
854
|
-
if self.
|
|
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.
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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
|
-
|
|
881
|
-
|
|
995
|
+
image : bytes
|
|
996
|
+
Buyte stream of the image.
|
|
882
997
|
"""
|
|
883
|
-
verify( not self.
|
|
998
|
+
verify( not self._closed, "Cannot call savefig() after close() was called")
|
|
884
999
|
img_buf = io.BytesIO()
|
|
885
|
-
if self.
|
|
1000
|
+
if self._fig is None:
|
|
886
1001
|
self.render(draw=False)
|
|
887
|
-
self.
|
|
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
|
|
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,
|
|
910
|
-
If
|
|
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.
|
|
1029
|
+
if not self._closed:
|
|
915
1030
|
# magic wand to avoid printing an empty figure message
|
|
916
1031
|
if clear:
|
|
917
|
-
if not self.
|
|
1032
|
+
if not self._fig is None:
|
|
918
1033
|
def repr_magic(self):
|
|
919
|
-
return type(self)._repr_html_(self) if len(self.
|
|
920
|
-
self.
|
|
921
|
-
self.delaxes( self.
|
|
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.
|
|
925
|
-
plt.close(self.
|
|
926
|
-
self.
|
|
927
|
-
self.
|
|
928
|
-
self.
|
|
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
|
-
"""
|
|
938
|
-
while len(self.
|
|
939
|
-
self.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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,
|
|
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],
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
1075
|
-
|
|
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
|
|
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
|
-
|
|
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/>`__
|
|
1428
|
+
""" Iterator for `xkcd <https://xkcd.com/color/rgb/>`__ matplotlib colors """
|
|
1311
1429
|
return colors("xkcd")
|
|
1312
1430
|
|
cdxcore/uniquehash.py
CHANGED
|
@@ -1096,7 +1096,7 @@ def unique_hash64( *args, **kwargs ) -> str:
|
|
|
1096
1096
|
def named_unique_filename48_8( label : str, *args, **kwargs ) -> str:
|
|
1097
1097
|
"""
|
|
1098
1098
|
Returns a unique and valid filename which is composed of `label` and a unique ID
|
|
1099
|
-
computed using all of `label`, `args`, and `kwargs`.
|
|
1099
|
+
computed using all of `label`, `args`, and `kwargs`. ``label`` is not assumed to be unique.
|
|
1100
1100
|
|
|
1101
1101
|
Consider a use cases where an experiment defined by ``definition``
|
|
1102
1102
|
has produced ``results`` which we wish to :mod:`pickle` to disk.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
cdxcore/__init__.py,sha256=
|
|
1
|
+
cdxcore/__init__.py,sha256=D8piCd2zM0TmYrjaCAVbv9v66LizCZ2Gj-O1clXJh8w,127
|
|
2
2
|
cdxcore/config.py,sha256=RaxpAnTEyGXLiq-1O2prXFBb2sdyqB6hHDHPAFGe2k0,98472
|
|
3
3
|
cdxcore/deferred.py,sha256=41buUit1SOAnhVSrzlzM5hd2OOhnAS2QRDSWEpjXfdE,43138
|
|
4
4
|
cdxcore/dynalimits.py,sha256=TXEwa4wCdeM5epG1ceezaHwvmhnbU-oXlXpHNbOMQR8,11293
|
|
5
|
-
cdxcore/dynaplot.py,sha256=
|
|
5
|
+
cdxcore/dynaplot.py,sha256=Qj1LjEzx12sKUyLvioKNVAF2juuq0SYaofwtRXZ4AWA,56727
|
|
6
6
|
cdxcore/err.py,sha256=3JP1IZDMIKayTt-ZNV9PCYdjtvkybQTjZOrRmBZWyfg,14709
|
|
7
7
|
cdxcore/filelock.py,sha256=D2U8rzxYkeuDc8RLRbePBQ939PPxcJuRC8sahmVx-x8,34781
|
|
8
8
|
cdxcore/jcpool.py,sha256=Vw8o8S4_qTVQNIr2sRaBR_kHz_wO0zpYs0QjlKSYFz8,27280
|
|
@@ -10,11 +10,11 @@ cdxcore/npio.py,sha256=SVpKkFt6lyRogkns-oky0wEiWgVTalgB0OdaSvyWtlU,24212
|
|
|
10
10
|
cdxcore/npshm.py,sha256=9buYPNJoNlw69NZQ-nLF13PEWBWNx51a0gjQw5Gc24U,18368
|
|
11
11
|
cdxcore/pretty.py,sha256=FsI62rlaqRX1E-uPCSnu0M4UoCQ5Z55TDvYPnzTNO70,17220
|
|
12
12
|
cdxcore/subdir.py,sha256=2AFW0BJ2tP6Xt40SEYTgd3diC3RlObHxVswrLHD6Auo,191404
|
|
13
|
-
cdxcore/uniquehash.py,sha256=
|
|
13
|
+
cdxcore/uniquehash.py,sha256=UlusNrQ1AKmbF542FNXM42f1Wy5OhujCpwsV3M9ARbw,50866
|
|
14
14
|
cdxcore/util.py,sha256=dqCEhrDvUM4qjeUwb6n8jPfS8WC4Navcj83gDjXfRFE,39349
|
|
15
15
|
cdxcore/verbose.py,sha256=vsjGTVnAHMPg2L2RfsowWKKPjUSnQJ3F653vDTydBkI,30223
|
|
16
16
|
cdxcore/version.py,sha256=pmbFIZ6Egif_ppZNFJRqEZO0HBffIzkn-hkY6HpkMpU,27325
|
|
17
|
-
cdxcore-0.1.
|
|
17
|
+
cdxcore-0.1.32.dist-info/licenses/LICENSE,sha256=M-cisgK9kb1bqVRJ7vrCxHcMQQfDxdY3c2YFJJWfNQg,1090
|
|
18
18
|
docs/source/conf.py,sha256=yn3LYgw3sT45mUyll-B2emVp6jg7H6KfAHOcBg_MNv4,4182
|
|
19
19
|
tests/test_config.py,sha256=N86mH3y7k3LXEmU8uPLfrmRMZ-80VhlD35nBbpLmebg,15617
|
|
20
20
|
tests/test_deferred.py,sha256=4Xsb76r-XqHKiBuHa4jbErjMWbrgHXfPwewzzY4lf9Y,7922
|
|
@@ -34,7 +34,7 @@ tmp/npsh1.py,sha256=mNucUl2-jNmE84GlMlliB4aJ0UQ9FqdymgcY_9mLeZY,15432
|
|
|
34
34
|
tmp/sharedarray.py,sha256=dNOT1ObCc3nM3qA3OA508NcENIBnkmWMxRPCqvMVa8A,12862
|
|
35
35
|
up/git_message.py,sha256=EfSH7Pit3ZoCiRqSMwRCUN_QyuwreU4LTIyGSutBlm4,123
|
|
36
36
|
up/pip_modify_setup.py,sha256=Esaml4yA9tFsqxLhk5bWSwvKCURONjQqfyChgFV2TSY,1584
|
|
37
|
-
cdxcore-0.1.
|
|
38
|
-
cdxcore-0.1.
|
|
39
|
-
cdxcore-0.1.
|
|
40
|
-
cdxcore-0.1.
|
|
37
|
+
cdxcore-0.1.32.dist-info/METADATA,sha256=6VhejknP70ijpwuldyWP2iZrCANOBCEqZ--FYvYJwaU,5939
|
|
38
|
+
cdxcore-0.1.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
+
cdxcore-0.1.32.dist-info/top_level.txt,sha256=phNSwCyJFe7UP2YMoi8o6ykhotatlIbJHjTp9EHM51k,26
|
|
40
|
+
cdxcore-0.1.32.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|