wolfhece 2.1.98__py3-none-any.whl → 2.1.100__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.
- wolfhece/PyDraw.py +506 -35
- wolfhece/PyParams.py +18 -8
- wolfhece/PyVertex.py +12 -5
- wolfhece/PyVertexvectors.py +27 -16
- wolfhece/Results2DGPU.py +23 -7
- wolfhece/acceptability/Parallels.py +2 -2
- wolfhece/acceptability/_add_path.py +23 -0
- wolfhece/acceptability/acceptability.py +594 -563
- wolfhece/acceptability/acceptability_gui.py +564 -331
- wolfhece/acceptability/cli.py +307 -120
- wolfhece/acceptability/func.py +1743 -1597
- wolfhece/analyze_vect.py +177 -0
- wolfhece/apps/version.py +1 -1
- wolfhece/bernoulli/losses.py +75 -22
- wolfhece/bernoulli/losses_jax.py +143 -0
- wolfhece/bernoulli/pipe.py +7 -2
- wolfhece/math_parser/__init__.py +4 -4
- wolfhece/math_parser/calculator.py +50 -9
- wolfhece/matplotlib_fig.py +1980 -0
- wolfhece/mesh2d/simple_2d.py +2399 -0
- wolfhece/mesh2d/wolf2dprev.py +1 -1
- wolfhece/pidcontroller.py +131 -0
- wolfhece/pywalous.py +7 -7
- wolfhece/scenario/config_manager.py +191 -83
- wolfhece/wolf_array.py +162 -109
- wolfhece/wolf_vrt.py +108 -7
- wolfhece/wolfresults_2D.py +109 -4
- wolfhece/xyz_file.py +91 -51
- {wolfhece-2.1.98.dist-info → wolfhece-2.1.100.dist-info}/METADATA +1 -1
- {wolfhece-2.1.98.dist-info → wolfhece-2.1.100.dist-info}/RECORD +33 -27
- {wolfhece-2.1.98.dist-info → wolfhece-2.1.100.dist-info}/WHEEL +1 -1
- {wolfhece-2.1.98.dist-info → wolfhece-2.1.100.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.98.dist-info → wolfhece-2.1.100.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1980 @@
|
|
1
|
+
from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavigationToolbar
|
2
|
+
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
|
3
|
+
from typing import Literal
|
4
|
+
from matplotlib.figure import Figure
|
5
|
+
import matplotlib.pyplot as plt
|
6
|
+
from matplotlib.gridspec import GridSpec
|
7
|
+
import numpy as np
|
8
|
+
from matplotlib.axes import Axes
|
9
|
+
from wolfhece.CpGrid import CpGrid
|
10
|
+
from wolfhece.PyParams import Wolf_Param, new_json
|
11
|
+
from wolfhece.PyTranslate import _
|
12
|
+
from wolfhece.PyVertex import getRGBfromI
|
13
|
+
|
14
|
+
|
15
|
+
import wx
|
16
|
+
from matplotlib.backend_bases import KeyEvent, MouseEvent
|
17
|
+
from matplotlib.lines import Line2D
|
18
|
+
|
19
|
+
|
20
|
+
import logging
|
21
|
+
import json
|
22
|
+
from pathlib import Path
|
23
|
+
from enum import Enum
|
24
|
+
|
25
|
+
|
26
|
+
def sanitize_fmt(fmt):
|
27
|
+
"""
|
28
|
+
Sanitizes the given format string for numerical formatting.
|
29
|
+
This function ensures that the format string is in a valid format
|
30
|
+
for floating-point number representation. If the input format string
|
31
|
+
is 'None' or an empty string, it defaults to '.2f'. Otherwise, it
|
32
|
+
ensures that the format string contains a decimal point and ends with
|
33
|
+
'f' for floating-point representation. If the format string is '.f',
|
34
|
+
it defaults to '.2f'.
|
35
|
+
|
36
|
+
:param fmt: The format string to be sanitized.
|
37
|
+
:type fmt: str
|
38
|
+
:return: A sanitized format string suitable for floating-point number formatting.
|
39
|
+
:rtype: str
|
40
|
+
"""
|
41
|
+
if fmt in ['None', '']:
|
42
|
+
return '.2f'
|
43
|
+
else:
|
44
|
+
if not '.' in fmt:
|
45
|
+
fmt = '.' + fmt
|
46
|
+
|
47
|
+
if not 'f' in fmt:
|
48
|
+
fmt = fmt + 'f'
|
49
|
+
|
50
|
+
if fmt == '.f':
|
51
|
+
fmt = '.2f'
|
52
|
+
|
53
|
+
return fmt
|
54
|
+
|
55
|
+
|
56
|
+
class Matplotlib_ax_properties():
|
57
|
+
|
58
|
+
def __init__(self, ax =None) -> None:
|
59
|
+
|
60
|
+
self._ax = ax
|
61
|
+
self._myprops = None
|
62
|
+
self._lines:list[Matplolib_line_properties] = []
|
63
|
+
|
64
|
+
self._tmp_line_prop:Matplolib_line_properties = None
|
65
|
+
self._selected_line = -1
|
66
|
+
|
67
|
+
self.title = 'Figure'
|
68
|
+
self.xtitle = 'X [m]'
|
69
|
+
self.ytitle = 'Y [m]'
|
70
|
+
self.legend = False
|
71
|
+
self.xmin = 0.
|
72
|
+
self.xmax = 1.
|
73
|
+
self.ymin = 0.
|
74
|
+
self.ymax = 1.
|
75
|
+
self.gridx_major = False
|
76
|
+
self.gridy_major = False
|
77
|
+
self.gridx_minor = False
|
78
|
+
self.gridy_minor = False
|
79
|
+
|
80
|
+
self._equal_axis = 0
|
81
|
+
self.scaling_factor = 1.
|
82
|
+
|
83
|
+
self.ticks_x = 1.
|
84
|
+
self.ticks_y = 1.
|
85
|
+
self.ticks_label_x = 1.
|
86
|
+
self.ticks_label_y = 1.
|
87
|
+
|
88
|
+
self.format_x = '.2f'
|
89
|
+
self.format_y = '.2f'
|
90
|
+
|
91
|
+
self._set_props()
|
92
|
+
|
93
|
+
@property
|
94
|
+
def is_equal(self):
|
95
|
+
|
96
|
+
if self._equal_axis == 1:
|
97
|
+
return 'equal'
|
98
|
+
elif self._equal_axis == 0:
|
99
|
+
return 'auto'
|
100
|
+
else:
|
101
|
+
return self.scaling_factor
|
102
|
+
|
103
|
+
def reset_selection(self):
|
104
|
+
if self._selected_line>=0:
|
105
|
+
for curline in self._lines:
|
106
|
+
curline.selected = False
|
107
|
+
self._selected_line = -1
|
108
|
+
|
109
|
+
def select_line(self, idx:int):
|
110
|
+
|
111
|
+
if self._selected_line>=0:
|
112
|
+
self.reset_selection()
|
113
|
+
|
114
|
+
if idx>=0 and idx<len(self._lines):
|
115
|
+
self._selected_line = idx
|
116
|
+
self._lines[idx].selected = True
|
117
|
+
|
118
|
+
def set_ax(self, ax:Axes):
|
119
|
+
self._ax = ax
|
120
|
+
|
121
|
+
if ax is None:
|
122
|
+
return
|
123
|
+
|
124
|
+
self.get_properties()
|
125
|
+
|
126
|
+
self._lines = [Matplolib_line_properties(line, self) for line in ax.get_lines()]
|
127
|
+
|
128
|
+
return self
|
129
|
+
|
130
|
+
def _set_props(self):
|
131
|
+
""" Set the properties UI """
|
132
|
+
|
133
|
+
if self._myprops is not None:
|
134
|
+
return
|
135
|
+
|
136
|
+
self._myprops = Wolf_Param(title='Figure properties',
|
137
|
+
w= 500, h= 400,
|
138
|
+
to_read= False,
|
139
|
+
ontop= False,
|
140
|
+
init_GUI= False)
|
141
|
+
|
142
|
+
self._myprops.set_callbacks(None, self.destroyprop)
|
143
|
+
|
144
|
+
# self._myprops.hide_selected_buttons() # only 'Apply' button
|
145
|
+
|
146
|
+
self._myprops.addparam('Draw','Title',self.title,'String','Title')
|
147
|
+
self._myprops.addparam('Draw','X title',self.xtitle,'String','X title')
|
148
|
+
self._myprops.addparam('Draw','Y title',self.ytitle,'String','Y title')
|
149
|
+
self._myprops.addparam('Draw','Legend',self.legend,'Logical','Legend')
|
150
|
+
|
151
|
+
self._myprops.addparam('Bounds','X min',self.xmin,'Float','X min')
|
152
|
+
self._myprops.addparam('Bounds','X max',self.xmax,'Float','X max')
|
153
|
+
self._myprops.addparam('Bounds','Y min',self.ymin,'Float','Y min')
|
154
|
+
self._myprops.addparam('Bounds','Y max',self.ymax,'Float','Y max')
|
155
|
+
|
156
|
+
self._myprops.addparam('Ticks X','Positions',self.ticks_x,'String','X ticks')
|
157
|
+
self._myprops.addparam('Ticks X','Labels',self.ticks_label_x,'String','X ticks labels')
|
158
|
+
|
159
|
+
self._myprops.addparam('Ticks Y','Positions',self.ticks_y,'String','Y ticks')
|
160
|
+
self._myprops.addparam('Ticks Y','Labels',self.ticks_label_y,'String','Y ticks labels')
|
161
|
+
|
162
|
+
self._myprops.addparam('Formats','Ticks X',self.format_x,'String','X format')
|
163
|
+
self._myprops.addparam('Formats','Ticks Y',self.format_y,'String','Y format')
|
164
|
+
self._myprops.addparam('Formats','Shape',self._equal_axis,'Integer','Shape', jsonstr= new_json({'auto':0, 'equal':1, 'specific':2}))
|
165
|
+
self._myprops.addparam('Formats','Scaling factor',self.scaling_factor,'Float','Scaling factor')
|
166
|
+
|
167
|
+
self._myprops.add_param('Grid','Major X', self.gridx_major, 'Logical', 'Major grid X')
|
168
|
+
self._myprops.add_param('Grid','Major Y', self.gridy_major, 'Logical', 'Major grid Y')
|
169
|
+
|
170
|
+
self._myprops.Populate()
|
171
|
+
|
172
|
+
def populate(self):
|
173
|
+
""" Populate the properties UI """
|
174
|
+
|
175
|
+
if self._myprops is None:
|
176
|
+
self._set_props()
|
177
|
+
|
178
|
+
self._myprops[('Draw','Title')] = self.title
|
179
|
+
self._myprops[('Draw','X title')] = self.xtitle
|
180
|
+
self._myprops[('Draw','Y title')] = self.ytitle
|
181
|
+
self._myprops[('Draw','Legend')] = self.legend
|
182
|
+
|
183
|
+
self._myprops[('Bounds','X min')] = self.xmin
|
184
|
+
self._myprops[('Bounds','X max')] = self.xmax
|
185
|
+
self._myprops[('Bounds','Y min')] = self.ymin
|
186
|
+
self._myprops[('Bounds','Y max')] = self.ymax
|
187
|
+
|
188
|
+
self._myprops[('Grid','Major X')] = self.gridx_major
|
189
|
+
self._myprops[('Grid','Major Y')] = self.gridy_major
|
190
|
+
|
191
|
+
self._myprops[('Ticks X','Positions')] = self.ticks_x
|
192
|
+
self._myprops[('Ticks X','Labels')] = self.ticks_label_x
|
193
|
+
|
194
|
+
self._myprops[('Ticks Y','Positions')] = self.ticks_y
|
195
|
+
self._myprops[('Ticks Y','Labels')] = self.ticks_label_y
|
196
|
+
|
197
|
+
self._myprops[('Formats','Ticks X')] = self.format_x
|
198
|
+
self._myprops[('Formats','Ticks Y')] = self.format_y
|
199
|
+
|
200
|
+
self._myprops[('Formats','Shape')] = self._equal_axis
|
201
|
+
self._myprops[('Formats','Scaling factor')] = self.scaling_factor
|
202
|
+
|
203
|
+
self._myprops.Populate()
|
204
|
+
|
205
|
+
def ui(self):
|
206
|
+
|
207
|
+
if self._myprops is not None:
|
208
|
+
self._myprops.CenterOnScreen()
|
209
|
+
self._myprops.Raise()
|
210
|
+
self._myprops.Show()
|
211
|
+
return
|
212
|
+
|
213
|
+
self._set_props()
|
214
|
+
|
215
|
+
self._myprops.Show()
|
216
|
+
|
217
|
+
self._myprops.SetTitle(_('Ax properties'))
|
218
|
+
|
219
|
+
icon = wx.Icon()
|
220
|
+
icon_path = Path(__file__).parent / "apps/wolf_logo2.bmp"
|
221
|
+
icon.CopyFromBitmap(wx.Bitmap(str(icon_path), wx.BITMAP_TYPE_ANY))
|
222
|
+
self._myprops.SetIcon(icon)
|
223
|
+
|
224
|
+
self._myprops.Center()
|
225
|
+
self._myprops.Raise()
|
226
|
+
|
227
|
+
def destroyprop(self):
|
228
|
+
self._myprops=None
|
229
|
+
|
230
|
+
def bounds_lines(self):
|
231
|
+
|
232
|
+
if self._ax is None:
|
233
|
+
logging.warning('No axes found')
|
234
|
+
return
|
235
|
+
|
236
|
+
lines = self._ax.get_lines()
|
237
|
+
|
238
|
+
if len(lines) == 0:
|
239
|
+
logging.warning('No lines found')
|
240
|
+
return
|
241
|
+
|
242
|
+
xmin = np.inf
|
243
|
+
xmax = -np.inf
|
244
|
+
ymin = np.inf
|
245
|
+
ymax = -np.inf
|
246
|
+
|
247
|
+
for line in lines:
|
248
|
+
x = line.get_xdata()
|
249
|
+
y = line.get_ydata()
|
250
|
+
|
251
|
+
xmin = min(xmin, np.min(x))
|
252
|
+
xmax = max(xmax, np.max(x))
|
253
|
+
ymin = min(ymin, np.min(y))
|
254
|
+
ymax = max(ymax, np.max(y))
|
255
|
+
|
256
|
+
return xmin, xmax, ymin, ymax
|
257
|
+
|
258
|
+
def fill_property(self, verbosity= True):
|
259
|
+
|
260
|
+
if self._myprops is None:
|
261
|
+
logging.warning('Properties UI not found')
|
262
|
+
return
|
263
|
+
|
264
|
+
self._myprops.apply_changes_to_memory(verbosity= verbosity)
|
265
|
+
|
266
|
+
self.title = self._myprops[('Draw','Title')]
|
267
|
+
self.xtitle = self._myprops[('Draw','X title')]
|
268
|
+
self.ytitle = self._myprops[('Draw','Y title')]
|
269
|
+
self.legend = self._myprops[('Draw','Legend')]
|
270
|
+
|
271
|
+
self.xmin = self._myprops[('Bounds','X min')]
|
272
|
+
self.xmax = self._myprops[('Bounds','X max')]
|
273
|
+
self.ymin = self._myprops[('Bounds','Y min')]
|
274
|
+
self.ymax = self._myprops[('Bounds','Y max')]
|
275
|
+
|
276
|
+
self.gridx_major = self._myprops[('Grid','Major X')]
|
277
|
+
self.gridy_major = self._myprops[('Grid','Major Y')]
|
278
|
+
|
279
|
+
xmin, xmax, ymin, ymax = self.bounds_lines()
|
280
|
+
|
281
|
+
if self.xmin == -99999.:
|
282
|
+
self.xmin = xmin
|
283
|
+
if self.xmax == -99999.:
|
284
|
+
self.xmax = xmax
|
285
|
+
if self.ymin == -99999.:
|
286
|
+
self.ymin = ymin
|
287
|
+
if self.ymax == -99999.:
|
288
|
+
self.ymax = ymax
|
289
|
+
|
290
|
+
self.format_x = sanitize_fmt(self._myprops[('Formats','Ticks X')])
|
291
|
+
self.format_y = sanitize_fmt(self._myprops[('Formats','Ticks Y')])
|
292
|
+
|
293
|
+
def format_value(value, fmt):
|
294
|
+
return '{value:{fmt}}'.format(value=value, fmt=fmt)
|
295
|
+
|
296
|
+
ticks_x = self._myprops[('Ticks X','Positions')]
|
297
|
+
if '[' in ticks_x:
|
298
|
+
self.ticks_x = [float(cur.replace("'",'').replace(',','')) for cur in self._myprops[('Ticks X','Positions')].replace('[','').replace(']','').split()]
|
299
|
+
else:
|
300
|
+
try:
|
301
|
+
self.ticks_x = float(ticks_x)
|
302
|
+
self.ticks_x = np.linspace(self.xmin, self.xmax, int(np.ceil((self.xmax-self.xmin)/self.ticks_x)+1), endpoint=True).tolist()
|
303
|
+
except:
|
304
|
+
self.ticks_x = np.linspace(self.xmin, self.xmax, 5).tolist()
|
305
|
+
|
306
|
+
ticks_label_x = self._myprops[('Ticks X','Labels')]
|
307
|
+
if '[' in ticks_label_x:
|
308
|
+
self.ticks_label_x = [cur.replace("'",'').replace(',','') for cur in self._myprops[('Ticks X','Labels')].replace('[','').replace(']','').split()]
|
309
|
+
if len(self.ticks_label_x) != len(self.ticks_x):
|
310
|
+
self.ticks_label_x = [format_value(cur, self.format_x) for cur in self.ticks_x]
|
311
|
+
else:
|
312
|
+
self.ticks_label_x = [format_value(cur, self.format_x) for cur in self.ticks_x]
|
313
|
+
|
314
|
+
ticks_y = self._myprops[('Ticks Y','Positions')]
|
315
|
+
if '[' in ticks_y:
|
316
|
+
self.ticks_y = [float(cur.replace("'",'').replace(',','')) for cur in self._myprops[('Ticks Y','Positions')].replace('[','').replace(']','').split()]
|
317
|
+
else:
|
318
|
+
try:
|
319
|
+
self.ticks_y = float(ticks_y)
|
320
|
+
self.ticks_y = np.linspace(self.ymin, self.ymax, int(np.ceil((self.ymax-self.ymin)/self.ticks_y)+1), endpoint= True).tolist()
|
321
|
+
except:
|
322
|
+
self.ticks_y = np.linspace(self.ymin, self.ymax, 5).tolist()
|
323
|
+
|
324
|
+
ticks_label_y = self._myprops[('Ticks Y','Labels')]
|
325
|
+
if '[' in ticks_label_y:
|
326
|
+
self.ticks_label_y = [cur.replace("'",'').replace(',','') for cur in self._myprops[('Ticks Y','Labels')].replace('[','').replace(']','').split()]
|
327
|
+
if len(self.ticks_label_y) != len(self.ticks_y):
|
328
|
+
self.ticks_label_y = [format_value(cur, self.format_y) for cur in self.ticks_y]
|
329
|
+
else:
|
330
|
+
self.ticks_label_y = [format_value(cur, self.format_y) for cur in self.ticks_y]
|
331
|
+
|
332
|
+
self._equal_axis = self._myprops[('Formats','Shape')]
|
333
|
+
self.scaling_factor = self._myprops[('Formats','Scaling factor')]
|
334
|
+
|
335
|
+
self.set_properties()
|
336
|
+
|
337
|
+
def set_properties(self, ax:Axes = None):
|
338
|
+
|
339
|
+
if ax is None:
|
340
|
+
ax = self._ax
|
341
|
+
|
342
|
+
ax.set_title(self.title)
|
343
|
+
ax.set_xlabel(self.xtitle)
|
344
|
+
ax.set_ylabel(self.ytitle)
|
345
|
+
|
346
|
+
ax.xaxis.grid(self.gridx_major)
|
347
|
+
ax.yaxis.grid(self.gridy_major)
|
348
|
+
|
349
|
+
ax.set_xticks(self.ticks_x, self.ticks_label_x)
|
350
|
+
ax.set_yticks(self.ticks_y, self.ticks_label_y)
|
351
|
+
|
352
|
+
if self.legend:
|
353
|
+
update = any(line.update_legend for line in self._lines)
|
354
|
+
if update:
|
355
|
+
ax.legend().set_visible(False)
|
356
|
+
for line in self._lines:
|
357
|
+
line.update_legend = True
|
358
|
+
ax.legend().set_visible(True)
|
359
|
+
else:
|
360
|
+
ax.legend().set_visible(False)
|
361
|
+
|
362
|
+
ax.set_aspect(self.is_equal)
|
363
|
+
|
364
|
+
ax.set_xlim(self.xmin, self.xmax)
|
365
|
+
ax.set_ylim(self.ymin, self.ymax)
|
366
|
+
|
367
|
+
ax.figure.canvas.draw()
|
368
|
+
|
369
|
+
self.get_properties()
|
370
|
+
|
371
|
+
def get_properties(self, ax:Axes = None):
|
372
|
+
|
373
|
+
if ax is None:
|
374
|
+
ax = self._ax
|
375
|
+
|
376
|
+
self.title = ax.get_title()
|
377
|
+
self.xtitle = ax.get_xlabel()
|
378
|
+
self.ytitle = ax.get_ylabel()
|
379
|
+
self.legend = ax.legend().get_visible()
|
380
|
+
self.xmin, self.xmax = ax.get_xlim()
|
381
|
+
self.ymin, self.ymax = ax.get_ylim()
|
382
|
+
|
383
|
+
self.gridx_major = any(line.get_visible() for line in ax.get_xgridlines())
|
384
|
+
self.gridy_major = any(line.get_visible() for line in ax.get_ygridlines())
|
385
|
+
|
386
|
+
self.ticks_x = [str(cur) for cur in ax.get_xticks()]
|
387
|
+
self.ticks_y = [str(cur) for cur in ax.get_yticks()]
|
388
|
+
|
389
|
+
self.ticks_label_x = [label.get_text().replace("'",'').replace(',','') for label in ax.get_xticklabels()]
|
390
|
+
self.ticks_label_y = [label.get_text().replace("'",'').replace(',','') for label in ax.get_yticklabels()]
|
391
|
+
|
392
|
+
if ax.get_aspect() == 'auto':
|
393
|
+
self._equal_axis = 0
|
394
|
+
self.scaling_factor = 1.
|
395
|
+
elif ax.get_aspect() == 1.:
|
396
|
+
self._equal_axis = 1
|
397
|
+
self.scaling_factor = 1.
|
398
|
+
else:
|
399
|
+
self._equal_axis = 2
|
400
|
+
self.scaling_factor = ax.get_aspect()
|
401
|
+
logging.warning('Aspect ratio not found, set to auto')
|
402
|
+
|
403
|
+
self.populate()
|
404
|
+
|
405
|
+
def to_dict(self) -> str:
|
406
|
+
""" properties to dict """
|
407
|
+
|
408
|
+
props= {'title':self.title,
|
409
|
+
'xtitle':self.xtitle,
|
410
|
+
'ytitle':self.ytitle,
|
411
|
+
'legend':self.legend,
|
412
|
+
'xmin':self.xmin,
|
413
|
+
'xmax':self.xmax,
|
414
|
+
'ymin':self.ymin,
|
415
|
+
'ymax':self.ymax,
|
416
|
+
'ticks_x':self.ticks_x,
|
417
|
+
'ticks_y':self.ticks_y,
|
418
|
+
'ticks_label_x':self.ticks_label_x,
|
419
|
+
'ticks_label_y':self.ticks_label_y}
|
420
|
+
|
421
|
+
if self._lines is not None:
|
422
|
+
props['lines'] = [line.to_dict() for line in self._lines]
|
423
|
+
else:
|
424
|
+
props['lines'] = []
|
425
|
+
|
426
|
+
return props
|
427
|
+
|
428
|
+
def from_dict(self, props:dict, frame:wx.Frame = None):
|
429
|
+
""" properties from dict """
|
430
|
+
|
431
|
+
keys = ['title', 'xtitle', 'ytitle', 'legend', 'xmin', 'xmax', 'ymin', 'ymax', 'ticks_x', 'ticks_y', 'ticks_label_x', 'ticks_label_y']
|
432
|
+
|
433
|
+
for key in keys:
|
434
|
+
try:
|
435
|
+
setattr(self, key, props[key])
|
436
|
+
except:
|
437
|
+
logging.warning('Key not found in properties dict')
|
438
|
+
pass
|
439
|
+
|
440
|
+
if isinstance(self.ticks_x,list):
|
441
|
+
self.ticks_x = [float(cur) for cur in props['ticks_x']]
|
442
|
+
elif isinstance(self.ticks_x,float):
|
443
|
+
self.ticks_x = [self.ticks_x]
|
444
|
+
elif isinstance(self.ticks_x,str):
|
445
|
+
self.ticks_x = [float(self.ticks_x)]
|
446
|
+
|
447
|
+
if isinstance(self.ticks_y,list):
|
448
|
+
self.ticks_y = [float(cur) for cur in props['ticks_y']]
|
449
|
+
elif isinstance(self.ticks_y,float):
|
450
|
+
self.ticks_y = [self.ticks_y]
|
451
|
+
elif isinstance(self.ticks_y,str):
|
452
|
+
self.ticks_y = [float(self.ticks_y)]
|
453
|
+
|
454
|
+
if isinstance(self.ticks_label_x,list):
|
455
|
+
pass
|
456
|
+
elif isinstance(self.ticks_label_x,float):
|
457
|
+
self.ticks_label_x = [self.ticks_label_x]
|
458
|
+
elif isinstance(self.ticks_label_x,str):
|
459
|
+
self.ticks_label_x = [self.ticks_label_x]
|
460
|
+
|
461
|
+
if isinstance(self.ticks_label_y,list):
|
462
|
+
pass
|
463
|
+
elif isinstance(self.ticks_label_y,float):
|
464
|
+
self.ticks_label_y = [self.ticks_label_y]
|
465
|
+
elif isinstance(self.ticks_label_y,str):
|
466
|
+
self.ticks_label_y = [self.ticks_label_y]
|
467
|
+
|
468
|
+
assert len(self.ticks_x) == len(self.ticks_label_x), f'{len(self.ticks_x)} != {len(self.ticks_label_x)}'
|
469
|
+
assert len(self.ticks_y) == len(self.ticks_label_y), f'{len(self.ticks_y)} != {len(self.ticks_label_y)}'
|
470
|
+
|
471
|
+
for line in props['lines']:
|
472
|
+
if 'xdata' in line and 'ydata' in line:
|
473
|
+
xdata = line['xdata']
|
474
|
+
ydata = line['ydata']
|
475
|
+
self._ax.plot(xdata, ydata)
|
476
|
+
|
477
|
+
self.populate()
|
478
|
+
|
479
|
+
self._lines = [Matplolib_line_properties(line, self).from_dict(line_props) for line_props, line in zip(props['lines'], self._ax.get_lines())]
|
480
|
+
|
481
|
+
return self
|
482
|
+
|
483
|
+
def serialize(self):
|
484
|
+
""" Serialize the properties """
|
485
|
+
|
486
|
+
return json.dumps(self.to_dict(), indent=4)
|
487
|
+
|
488
|
+
def deserialize(self, props:str):
|
489
|
+
""" Deserialize the properties """
|
490
|
+
|
491
|
+
self.from_dict(json.loads(props))
|
492
|
+
|
493
|
+
def add_props_to_sizer(self, frame:wx.Frame, sizer:wx.BoxSizer):
|
494
|
+
""" Add the properties to a sizer """
|
495
|
+
|
496
|
+
self._myprops.ensure_prop(frame, show_in_active_if_default=True, height=300)
|
497
|
+
sizer.Add(self._myprops.prop, proportion= 1, flag= wx.EXPAND)
|
498
|
+
|
499
|
+
self._myprops.prop.Hide()
|
500
|
+
|
501
|
+
def show_props(self):
|
502
|
+
""" Show the properties """
|
503
|
+
|
504
|
+
self._myprops.prop.Show()
|
505
|
+
|
506
|
+
def hide_props(self):
|
507
|
+
""" Hide the properties """
|
508
|
+
|
509
|
+
self._myprops.prop.Hide()
|
510
|
+
|
511
|
+
def hide_all_props(self):
|
512
|
+
""" Hide all properties """
|
513
|
+
|
514
|
+
self.hide_props()
|
515
|
+
|
516
|
+
for line in self._lines:
|
517
|
+
line.hide_props()
|
518
|
+
|
519
|
+
def del_line(self, idx:int):
|
520
|
+
""" Delete a line """
|
521
|
+
|
522
|
+
if idx>=0 and idx<len(self._lines):
|
523
|
+
self._lines[idx].delete()
|
524
|
+
self._lines.pop(idx)
|
525
|
+
self._ax.lines.pop(idx)
|
526
|
+
|
527
|
+
|
528
|
+
MARKERS_MPL = ['None','o', 'v', '^', '<', '>', 's', 'p', 'P', '*', 'h', 'H', '+', 'x', 'X', 'D', 'd', '|', '_']
|
529
|
+
LINESTYLE_MPL = ['-', '--', '-.', ':', 'solid', 'dashed', 'dashdot', 'dotted', 'None']
|
530
|
+
|
531
|
+
|
532
|
+
def convert_colorname_rgb(color:str) -> str:
|
533
|
+
"""
|
534
|
+
Convert a given color name or abbreviation to its corresponding RGB tuple.
|
535
|
+
|
536
|
+
:param color: The color name or abbreviation to convert.
|
537
|
+
Supported colors are 'b'/'blue', 'g'/'green', 'r'/'red',
|
538
|
+
'c'/'cyan', 'm'/'magenta', 'y'/'yellow', 'k'/'black',
|
539
|
+
'w'/'white', and 'o'/'orange'.
|
540
|
+
:type color: str
|
541
|
+
:return: A tuple representing the RGB values of the color. If the color is not
|
542
|
+
recognized, returns (0, 0, 0) which corresponds to black.
|
543
|
+
:rtype: tuple
|
544
|
+
"""
|
545
|
+
|
546
|
+
if color in COLORS_MPL:
|
547
|
+
if color in ['b', 'blue']:
|
548
|
+
return (0,0,255)
|
549
|
+
elif color in ['g', 'green']:
|
550
|
+
return (0,128,0)
|
551
|
+
elif color in ['r', 'red']:
|
552
|
+
return (255,0,0)
|
553
|
+
elif color in ['c', 'cyan']:
|
554
|
+
return (0,255,255)
|
555
|
+
elif color in ['m', 'magenta']:
|
556
|
+
return (255,0,255)
|
557
|
+
elif color in ['y', 'yellow']:
|
558
|
+
return (255,255,0)
|
559
|
+
elif color in ['k', 'black']:
|
560
|
+
return (0,0,0)
|
561
|
+
elif color in ['w', 'white']:
|
562
|
+
return (255,255,255)
|
563
|
+
elif color in ['o', 'orange']:
|
564
|
+
return (255,165,0)
|
565
|
+
else:
|
566
|
+
return(0,0,0)
|
567
|
+
|
568
|
+
|
569
|
+
def convert_color(value:str | tuple) -> tuple:
|
570
|
+
""" Convert a hex color to RGB """
|
571
|
+
|
572
|
+
if isinstance(value, tuple):
|
573
|
+
return tuple([int(cur*255) for cur in value])
|
574
|
+
elif isinstance(value, str):
|
575
|
+
if value.startswith('#'):
|
576
|
+
value = value.lstrip('#')
|
577
|
+
return tuple(int(value[i:i+2], 16) for i in (0, 2, 4))
|
578
|
+
else:
|
579
|
+
return convert_colorname_rgb(value)
|
580
|
+
else:
|
581
|
+
return (0,0,0)
|
582
|
+
|
583
|
+
|
584
|
+
class Matplolib_line_properties():
|
585
|
+
|
586
|
+
def __init__(self, line:Line2D=None, ax_props:"Matplotlib_ax_properties"= None) -> None:
|
587
|
+
|
588
|
+
self.wx_exits = wx.App.Get() is not None
|
589
|
+
|
590
|
+
self._ax_props = ax_props
|
591
|
+
|
592
|
+
self.color = (0,0,255)
|
593
|
+
self.linewidth = 1.5
|
594
|
+
self._linestyle = 0
|
595
|
+
self._marker = 0
|
596
|
+
self.markersize = 6
|
597
|
+
self.alpha = 1.0
|
598
|
+
self.label = 'Line'
|
599
|
+
self.markerfacecolor = (0,0,255)
|
600
|
+
self.markeredgecolor = (0,0,255)
|
601
|
+
self.markeredgewidth = 1.5
|
602
|
+
self.visible = True
|
603
|
+
self.zorder = 1
|
604
|
+
self.picker:bool = False
|
605
|
+
self.picker_radius:float = 5.0
|
606
|
+
|
607
|
+
self._selected = False
|
608
|
+
self._selected_prop:Matplolib_line_properties = None
|
609
|
+
|
610
|
+
self._myprops = None
|
611
|
+
self._line = line
|
612
|
+
|
613
|
+
self.update_legend = False
|
614
|
+
|
615
|
+
self._set_props()
|
616
|
+
|
617
|
+
if self._line is not None:
|
618
|
+
self.get_properties()
|
619
|
+
|
620
|
+
@property
|
621
|
+
def ax_props(self):
|
622
|
+
return self._ax_props
|
623
|
+
|
624
|
+
@ax_props.setter
|
625
|
+
def ax_props(self, value):
|
626
|
+
self._ax_props = value
|
627
|
+
|
628
|
+
@property
|
629
|
+
def ax(self):
|
630
|
+
return self._ax_props._ax
|
631
|
+
|
632
|
+
@property
|
633
|
+
def fig(self):
|
634
|
+
return self._ax_props._ax.figure
|
635
|
+
|
636
|
+
def copy(self):
|
637
|
+
new_prop = Matplolib_line_properties()
|
638
|
+
|
639
|
+
new_prop._ax_props = self._ax_props
|
640
|
+
|
641
|
+
new_prop.color = self.color
|
642
|
+
new_prop.linewidth = self.linewidth
|
643
|
+
new_prop._linestyle = self._linestyle
|
644
|
+
new_prop._marker = self._marker
|
645
|
+
new_prop.markersize = self.markersize
|
646
|
+
new_prop.alpha = self.alpha
|
647
|
+
new_prop.label = self.label
|
648
|
+
new_prop.markerfacecolor = self.markerfacecolor
|
649
|
+
new_prop.markeredgecolor = self.markeredgecolor
|
650
|
+
new_prop.markeredgewidth = self.markeredgewidth
|
651
|
+
new_prop.visible = self.visible
|
652
|
+
new_prop.zorder = self.zorder
|
653
|
+
new_prop.picker = self.picker
|
654
|
+
new_prop.picker_radius = self.picker_radius
|
655
|
+
|
656
|
+
return new_prop
|
657
|
+
|
658
|
+
def presets(self, preset:str):
|
659
|
+
""" Set the properties to a preset """
|
660
|
+
|
661
|
+
self.color = (0,0,255)
|
662
|
+
self.linewidth = 1.5
|
663
|
+
self._linestyle = 0
|
664
|
+
self._marker = 0
|
665
|
+
self.markersize = 6
|
666
|
+
self.alpha = 1.0
|
667
|
+
self.label = 'Line'
|
668
|
+
self.markerfacecolor = (0,0,255)
|
669
|
+
self.markeredgecolor = (0,0,255)
|
670
|
+
self.markeredgewidth = 1.5
|
671
|
+
self.visible = True
|
672
|
+
self.zorder = 1
|
673
|
+
self.picker = False
|
674
|
+
self.picker_radius = 5.0
|
675
|
+
|
676
|
+
if preset == 'default':
|
677
|
+
pass
|
678
|
+
elif preset == 'water':
|
679
|
+
self.color = (0,0,255)
|
680
|
+
self.linewidth = 2.5
|
681
|
+
self.label = 'Water'
|
682
|
+
elif preset == 'land':
|
683
|
+
self.color = (0,255,0)
|
684
|
+
self.linewidth = 2.5
|
685
|
+
self.label = 'Land'
|
686
|
+
elif preset == 'banks':
|
687
|
+
self.color = (128,128,128)
|
688
|
+
self.linestyle = 1
|
689
|
+
self.linewidth = 1.0
|
690
|
+
|
691
|
+
self.set_properties()
|
692
|
+
|
693
|
+
@property
|
694
|
+
def selected(self):
|
695
|
+
return self._selected
|
696
|
+
|
697
|
+
@selected.setter
|
698
|
+
def selected(self, value):
|
699
|
+
self._selected = value
|
700
|
+
self.set_properties()
|
701
|
+
|
702
|
+
@property
|
703
|
+
def linestyle(self):
|
704
|
+
return LINESTYLE_MPL[self._linestyle]
|
705
|
+
|
706
|
+
@linestyle.setter
|
707
|
+
def linestyle(self, value):
|
708
|
+
|
709
|
+
if isinstance(value, str):
|
710
|
+
if value in LINESTYLE_MPL:
|
711
|
+
self._linestyle = LINESTYLE_MPL.index(value)
|
712
|
+
else:
|
713
|
+
logging.warning('Line style not found, set to default')
|
714
|
+
self._linestyle = 0
|
715
|
+
elif isinstance(value, int):
|
716
|
+
self._linestyle = value
|
717
|
+
else:
|
718
|
+
logging.warning('Line style not found, set to default')
|
719
|
+
self._linestyle = 0
|
720
|
+
|
721
|
+
@property
|
722
|
+
def marker(self):
|
723
|
+
return MARKERS_MPL[self._marker]
|
724
|
+
|
725
|
+
@marker.setter
|
726
|
+
def marker(self, value):
|
727
|
+
if isinstance(value, str):
|
728
|
+
if value in MARKERS_MPL:
|
729
|
+
self._marker = MARKERS_MPL.index(value)
|
730
|
+
else:
|
731
|
+
logging.warning('Marker not found, set to default')
|
732
|
+
self._marker = 0
|
733
|
+
elif isinstance(value, int):
|
734
|
+
self._marker = value
|
735
|
+
else:
|
736
|
+
logging.warning('Marker not found, set to default')
|
737
|
+
self._marker = 0
|
738
|
+
|
739
|
+
def set_line(self, line:Line2D):
|
740
|
+
self._line = line
|
741
|
+
|
742
|
+
if line is None:
|
743
|
+
return
|
744
|
+
|
745
|
+
self.get_properties()
|
746
|
+
|
747
|
+
return self
|
748
|
+
|
749
|
+
|
750
|
+
def on_pick(self, line:Line2D, mouseevent:MouseEvent):
|
751
|
+
if mouseevent.button == 1:
|
752
|
+
pass
|
753
|
+
print(mouseevent.xdata, mouseevent.ydata)
|
754
|
+
|
755
|
+
# line.set_color('r')
|
756
|
+
# line.figure.canvas.draw()
|
757
|
+
|
758
|
+
return True, dict()
|
759
|
+
|
760
|
+
def get_properties(self, line:Line2D= None):
|
761
|
+
|
762
|
+
if line is None:
|
763
|
+
line = self._line
|
764
|
+
|
765
|
+
if line is None:
|
766
|
+
logging.warning('Line not found/defined')
|
767
|
+
return
|
768
|
+
|
769
|
+
self.color = convert_color(line.get_color())
|
770
|
+
self.linewidth = line.get_linewidth()
|
771
|
+
self.linestyle = line.get_linestyle()
|
772
|
+
|
773
|
+
if self.linestyle not in LINESTYLE_MPL:
|
774
|
+
self.linestyle = '-'
|
775
|
+
logging.warning('Line style not found, set to default')
|
776
|
+
|
777
|
+
self.marker = line.get_marker()
|
778
|
+
|
779
|
+
if self.marker not in MARKERS_MPL:
|
780
|
+
self.marker = 'o'
|
781
|
+
logging.warning('Marker not found, set to default')
|
782
|
+
|
783
|
+
self.markersize = line.get_markersize()
|
784
|
+
self.alpha = line.get_alpha() if line.get_alpha() is not None else 1.0
|
785
|
+
self.label = line.get_label()
|
786
|
+
self.markerfacecolor = convert_color(line.get_markerfacecolor())
|
787
|
+
self.markeredgecolor = convert_color(line.get_markeredgecolor())
|
788
|
+
self.markeredgewidth = line.get_markeredgewidth()
|
789
|
+
self.visible = line.get_visible()
|
790
|
+
self.zorder = line.get_zorder()
|
791
|
+
|
792
|
+
self.picker = line.get_picker() is not None
|
793
|
+
self.picker_radius = line.get_pickradius()
|
794
|
+
|
795
|
+
def _set_props(self):
|
796
|
+
""" Set the properties UI """
|
797
|
+
|
798
|
+
if self._myprops is not None:
|
799
|
+
return
|
800
|
+
|
801
|
+
self._myprops = Wolf_Param(title='Line properties', w= 500, h= 400, to_read= False, ontop= False, init_GUI= False)
|
802
|
+
|
803
|
+
self._myprops.set_callbacks(None, self.destroyprop)
|
804
|
+
|
805
|
+
# self._myprops.hide_selected_buttons() # only 'Apply' button
|
806
|
+
|
807
|
+
self._myprops.addparam('Draw','Color',self.color,'Color','Drawing color')
|
808
|
+
self._myprops.addparam('Draw','Width',self.linewidth,'Float','Drawing width')
|
809
|
+
self._myprops.addparam('Draw','Style',self._linestyle,'Integer','Drawing style', jsonstr= new_json({'-':0, '--':1, '-.':2, ':':3, 'None':8, 'solid': 0, 'dashed': 1, 'dashdot': 2, 'dotted': 3}))
|
810
|
+
self._myprops.addparam('Draw', 'Alpha', self.alpha, 'Float', 'Transparency')
|
811
|
+
self._myprops.addparam('Draw', 'Label', self.label, 'String', 'Label')
|
812
|
+
self._myprops.addparam('Draw', 'Visible', self.visible, 'Logical', 'Visible')
|
813
|
+
self._myprops.addparam('Draw', 'Zorder', self.zorder, 'Integer', 'Zorder')
|
814
|
+
|
815
|
+
self._myprops.addparam('Marker', 'Marker', self._marker, 'Integer', 'Marker style', jsonstr= new_json({'None':0, 'o': 1, 'v': 2, '^': 3, '<': 4, '>': 5, 's': 6, 'p': 7, 'P': 8, '*': 9, 'h': 10, 'H': 11, '+': 12, 'x': 13, 'X': 14, 'D': 15, 'd': 16, '|': 17, '_': 18}))
|
816
|
+
self._myprops.addparam('Marker', 'Markersize', self.markersize, 'Float', 'Marker size')
|
817
|
+
self._myprops.addparam('Marker', 'Markerfacecolor', self.markerfacecolor, 'Color', 'Marker face color')
|
818
|
+
self._myprops.addparam('Marker', 'Markeredgecolor', self.markeredgecolor, 'Color', 'Marker edge color')
|
819
|
+
self._myprops.addparam('Marker', 'Markeredgewidth', self.markeredgewidth, 'Float', 'Marker edge width')
|
820
|
+
|
821
|
+
self._myprops.addparam('Picker', 'Picker', self.picker, 'Logical', 'Picker')
|
822
|
+
self._myprops.addparam('Picker', 'Picker radius', self.picker_radius, 'Float', 'Picker radius')
|
823
|
+
|
824
|
+
self._myprops.Populate()
|
825
|
+
# self._myprops.Layout()
|
826
|
+
# self._myprops.SetSizeHints(500,500)
|
827
|
+
|
828
|
+
def populate(self):
|
829
|
+
""" Populate the properties UI """
|
830
|
+
|
831
|
+
if self._myprops is None:
|
832
|
+
self._set_props()
|
833
|
+
|
834
|
+
self._myprops[('Draw','Color')] = self.color
|
835
|
+
self._myprops[('Draw','Width')] = self.linewidth
|
836
|
+
self._myprops[('Draw','Style')] = self._linestyle
|
837
|
+
self._myprops[('Draw','Alpha')] = self.alpha
|
838
|
+
self._myprops[('Draw','Label')] = self.label
|
839
|
+
self._myprops[('Draw','Visible')] = self.visible
|
840
|
+
self._myprops[('Draw','Zorder')] = self.zorder
|
841
|
+
|
842
|
+
self._myprops[('Marker', 'Marker')] = self._marker
|
843
|
+
self._myprops[('Marker', 'Markersize')] = self.markersize
|
844
|
+
self._myprops[('Marker', 'Markeredgecolor')] = self.markeredgecolor
|
845
|
+
self._myprops[('Marker', 'Markerfacecolor')] = self.markerfacecolor
|
846
|
+
self._myprops[('Marker', 'Markeredgewidth')] = self.markeredgewidth
|
847
|
+
|
848
|
+
self._myprops[('Picker', 'Picker')] = self.picker
|
849
|
+
self._myprops[('Picker', 'Picker radius')] = self.picker_radius
|
850
|
+
|
851
|
+
self._myprops.Populate()
|
852
|
+
|
853
|
+
def ui(self):
|
854
|
+
|
855
|
+
if self._myprops is not None:
|
856
|
+
self._myprops.CenterOnScreen()
|
857
|
+
self._myprops.Raise()
|
858
|
+
self._myprops.Show()
|
859
|
+
return
|
860
|
+
|
861
|
+
self._set_props()
|
862
|
+
|
863
|
+
self._myprops.Show()
|
864
|
+
self._myprops.SetTitle(_('Line properties'))
|
865
|
+
|
866
|
+
icon = wx.Icon()
|
867
|
+
icon_path = Path(__file__).parent / "apps/wolf_logo2.bmp"
|
868
|
+
icon.CopyFromBitmap(wx.Bitmap(str(icon_path), wx.BITMAP_TYPE_ANY))
|
869
|
+
self._myprops.SetIcon(icon)
|
870
|
+
|
871
|
+
self._myprops.Center()
|
872
|
+
self._myprops.Raise()
|
873
|
+
|
874
|
+
def destroyprop(self):
|
875
|
+
self._myprops=None
|
876
|
+
|
877
|
+
def fill_property(self, verbosity:bool= True):
|
878
|
+
|
879
|
+
if self._myprops is None:
|
880
|
+
logging.warning('Properties UI not found')
|
881
|
+
return
|
882
|
+
|
883
|
+
self._myprops.apply_changes_to_memory(verbosity= verbosity)
|
884
|
+
|
885
|
+
self.color = getRGBfromI(self._myprops[('Draw','Color')])
|
886
|
+
self.linewidth = self._myprops[('Draw','Width')]
|
887
|
+
self.linestyle = self._myprops[('Draw','Style')]
|
888
|
+
self.alpha = self._myprops[('Draw', 'Alpha')]
|
889
|
+
|
890
|
+
self.update_legend = self.label == self._myprops[('Draw', 'Label')]
|
891
|
+
|
892
|
+
self.label = self._myprops[('Draw', 'Label')]
|
893
|
+
self.visible = self._myprops[('Draw', 'Visible')]
|
894
|
+
self.zorder = self._myprops[('Draw', 'Zorder')]
|
895
|
+
|
896
|
+
self.marker = self._myprops[('Marker', 'Marker')]
|
897
|
+
self.markersize = self._myprops[('Marker', 'Markersize')]
|
898
|
+
self.markeredgecolor = getRGBfromI(self._myprops[('Marker', 'Markeredgecolor')])
|
899
|
+
self.markerfacecolor = getRGBfromI(self._myprops[('Marker', 'Markerfacecolor')])
|
900
|
+
self.markeredgewidth = self._myprops[('Marker', 'Markeredgewidth')]
|
901
|
+
|
902
|
+
self.picker = self._myprops[('Picker', 'Picker')]
|
903
|
+
self.picker_radius = self._myprops[('Picker', 'Picker radius')]
|
904
|
+
|
905
|
+
self.set_properties()
|
906
|
+
|
907
|
+
def set_properties(self, line:Line2D = None):
|
908
|
+
|
909
|
+
if line is None:
|
910
|
+
line = self._line
|
911
|
+
|
912
|
+
if line is None:
|
913
|
+
logging.warning('Line not found/defined')
|
914
|
+
return
|
915
|
+
|
916
|
+
def check_color(color):
|
917
|
+
if isinstance(color, str):
|
918
|
+
color = convert_colorname_rgb(color)
|
919
|
+
color = tuple([c/255. for c in color])
|
920
|
+
return color
|
921
|
+
|
922
|
+
line.set_color(check_color(self.color if not self.selected else (255,0,0)))
|
923
|
+
line.set_linewidth(self.linewidth if not self.selected else 3.0)
|
924
|
+
line.set_linestyle(self.linestyle if not self.selected else '-')
|
925
|
+
|
926
|
+
line.set_marker(self.marker)
|
927
|
+
line.set_markersize(self.markersize)
|
928
|
+
line.set_alpha(self.alpha)
|
929
|
+
line.set_label(self.label)
|
930
|
+
line.set_markerfacecolor(check_color(self.markerfacecolor if not self.selected else (255,0,0)))
|
931
|
+
line.set_markeredgecolor(check_color(self.markeredgecolor))
|
932
|
+
line.set_markeredgewidth(self.markeredgewidth)
|
933
|
+
|
934
|
+
line.set_visible(self.visible)
|
935
|
+
line.set_zorder(self.zorder)
|
936
|
+
|
937
|
+
line.set_pickradius(self.picker_radius)
|
938
|
+
|
939
|
+
line.set_picker(self.on_pick if self.picker else lambda line,mouseevent: (False, dict()))
|
940
|
+
|
941
|
+
if self._ax_props is not None:
|
942
|
+
self._ax_props.fill_property(verbosity= False)
|
943
|
+
else:
|
944
|
+
line.axes.figure.canvas.draw()
|
945
|
+
|
946
|
+
def show_properties(self):
|
947
|
+
self.ui()
|
948
|
+
|
949
|
+
def to_dict(self) -> str:
|
950
|
+
""" properties to dict """
|
951
|
+
|
952
|
+
xdata = self._line.get_xdata().tolist()
|
953
|
+
ydata = self._line.get_ydata().tolist()
|
954
|
+
|
955
|
+
return {'color':self.color,
|
956
|
+
'linewidth':self.linewidth,
|
957
|
+
'linestyle':self.linestyle,
|
958
|
+
'marker':self.marker,
|
959
|
+
'markersize':self.markersize,
|
960
|
+
'alpha':self.alpha,
|
961
|
+
'label':self.label,
|
962
|
+
'markerfacecolor':self.markerfacecolor,
|
963
|
+
'markeredgecolor':self.markeredgecolor,
|
964
|
+
'markeredgewidth':self.markeredgewidth,
|
965
|
+
'visible':self.visible,
|
966
|
+
'zorder':self.zorder,
|
967
|
+
'picker':self.picker,
|
968
|
+
'picker_radius':self.picker_radius,
|
969
|
+
'xdata':xdata,
|
970
|
+
'ydata':ydata}
|
971
|
+
|
972
|
+
def from_dict(self, props:dict):
|
973
|
+
""" properties from dict """
|
974
|
+
|
975
|
+
keys = ['color', 'linewidth', 'linestyle', 'marker', 'markersize', 'alpha', 'label', 'markerfacecolor', 'markeredgecolor', 'markeredgewidth', 'visible', 'zorder', 'picker', 'picker_radius']
|
976
|
+
|
977
|
+
for key in keys:
|
978
|
+
try:
|
979
|
+
setattr(self, key, props[key])
|
980
|
+
except:
|
981
|
+
logging.warning('Key not found in properties dict')
|
982
|
+
pass
|
983
|
+
|
984
|
+
self.populate()
|
985
|
+
self.set_properties()
|
986
|
+
|
987
|
+
return self
|
988
|
+
|
989
|
+
def add_props_to_sizer(self, frame:wx.Frame, sizer:wx.BoxSizer):
|
990
|
+
""" Add the properties to a sizer """
|
991
|
+
|
992
|
+
self._myprops.ensure_prop(frame, show_in_active_if_default=True, height=300)
|
993
|
+
sizer.Add(self._myprops.prop, proportion= 1, flag= wx.EXPAND)
|
994
|
+
self._myprops.prop.Hide()
|
995
|
+
|
996
|
+
def show_props(self):
|
997
|
+
""" Show the properties """
|
998
|
+
|
999
|
+
self.populate()
|
1000
|
+
self._myprops.prop.Show()
|
1001
|
+
|
1002
|
+
def hide_props(self):
|
1003
|
+
""" Hide the properties """
|
1004
|
+
|
1005
|
+
self._myprops.prop.Hide()
|
1006
|
+
|
1007
|
+
def delete(self):
|
1008
|
+
""" Delete the properties """
|
1009
|
+
|
1010
|
+
self._myprops.prop.Hide()
|
1011
|
+
self._myprops.prop.Destroy()
|
1012
|
+
self._myprops = None
|
1013
|
+
self._line = None
|
1014
|
+
|
1015
|
+
class PRESET_LAYOUTS(Enum):
|
1016
|
+
DEFAULT = (1,1)
|
1017
|
+
MAT2X2 = (2,2)
|
1018
|
+
class Matplotlib_Figure(wx.Frame):
|
1019
|
+
""" Matplotlib Figure with wx Frame """
|
1020
|
+
|
1021
|
+
def __init__(self, layout:tuple | list | dict | PRESET_LAYOUTS = None) -> None:
|
1022
|
+
"""
|
1023
|
+
Layout can be a tuple, a list, a dict or a string.
|
1024
|
+
If a string, it must be a list of strings or a list of lists. It will be used in fig.subplot_mosaic.
|
1025
|
+
If a tuple or a list of 2 integers. It will be used in fig.subplots.
|
1026
|
+
if a dict, it must contain 'nrows' and 'ncols' and 'ax_cells' (list of tuples with row_start, row_end, col_start, col_end, key).
|
1027
|
+
It will be used in fig.add_gridspec.
|
1028
|
+
|
1029
|
+
The class has:
|
1030
|
+
- fig: the figure
|
1031
|
+
- ax_dict: a dict of axes --> key: name of the axes, value: axes
|
1032
|
+
- ax: a list of axes --> always flatten
|
1033
|
+
|
1034
|
+
The properties of the figure can be accessed by self.fig_properties.
|
1035
|
+
The properties of the axes can be accessed by self._axes_properties.
|
1036
|
+
The current Axes can be accessed by self.cur_ax.
|
1037
|
+
|
1038
|
+
A plot can be added by self.add_plot(xdata, ydata, label, color, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, markeredgewidth, alpha, visible, zorder, picker, picker_radius)
|
1039
|
+
|
1040
|
+
:param layout: layout of the figure
|
1041
|
+
:type layout: tuple | list | dict | str
|
1042
|
+
"""
|
1043
|
+
|
1044
|
+
self.wx_exists = wx.App.Get() is not None
|
1045
|
+
|
1046
|
+
self.fig = plt.figure()
|
1047
|
+
dpi = self.fig.get_dpi()
|
1048
|
+
size_x, size_y = self.fig.get_size_inches()
|
1049
|
+
|
1050
|
+
if self.wx_exists:
|
1051
|
+
wx.Frame.__init__(self, None, -1, 'Matplotlib Figure', size=(size_x*dpi+16, size_y*dpi+240), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
|
1052
|
+
|
1053
|
+
self.ax_dict:dict[str,Axes] = {} # dict of axes
|
1054
|
+
self.ax:list[Axes] = [] # list of axes -- always flatten
|
1055
|
+
self.shown_props = None # shown properties
|
1056
|
+
|
1057
|
+
self.apply_layout(layout) # apply the layout
|
1058
|
+
pass
|
1059
|
+
|
1060
|
+
def presets(self, which:PRESET_LAYOUTS = PRESET_LAYOUTS.DEFAULT):
|
1061
|
+
""" Presets """
|
1062
|
+
|
1063
|
+
if which not in PRESET_LAYOUTS:
|
1064
|
+
logging.warning('Preset not found')
|
1065
|
+
return
|
1066
|
+
|
1067
|
+
self.apply_layout(which)
|
1068
|
+
|
1069
|
+
@property
|
1070
|
+
def layout(self):
|
1071
|
+
return self._layout
|
1072
|
+
|
1073
|
+
def apply_layout(self, layout:tuple | list | dict | PRESET_LAYOUTS):
|
1074
|
+
""" Apply the layout
|
1075
|
+
|
1076
|
+
Choose between (subplots, subplot_mosaic, gridspec) according to the type of layout (tuple, list[str], dict)
|
1077
|
+
"""
|
1078
|
+
|
1079
|
+
self._layout = layout
|
1080
|
+
|
1081
|
+
if self._layout is None:
|
1082
|
+
logging.info('No layout defined')
|
1083
|
+
return
|
1084
|
+
|
1085
|
+
if isinstance(layout, PRESET_LAYOUTS):
|
1086
|
+
|
1087
|
+
self.apply_layout(layout.value)
|
1088
|
+
return
|
1089
|
+
|
1090
|
+
if isinstance(layout, tuple | list):
|
1091
|
+
# check is the first element is a string - layout can be a list of lists
|
1092
|
+
tmp_layout = []
|
1093
|
+
for cur in layout:
|
1094
|
+
if isinstance(cur, list):
|
1095
|
+
tmp_layout.extend(cur)
|
1096
|
+
else:
|
1097
|
+
tmp_layout.append(cur)
|
1098
|
+
|
1099
|
+
if isinstance(tmp_layout[0], str):
|
1100
|
+
# List of strings - subplot_mosaic returns a dict of Axes
|
1101
|
+
self.ax_dict = self.fig.subplot_mosaic(layout)
|
1102
|
+
# store the axes in a list -- So we can access them by index, not only by name
|
1103
|
+
self.ax = [ax for ax in self.ax_dict.values()]
|
1104
|
+
else:
|
1105
|
+
# Tuple or list of 2 elements - subplots
|
1106
|
+
if len(layout) != 2:
|
1107
|
+
logging.warning('Layout must be a tuple or a list of 2 elements')
|
1108
|
+
return
|
1109
|
+
|
1110
|
+
self.nbrows, self.nbcols = layout
|
1111
|
+
if self.nbrows*self.nbcols == 1:
|
1112
|
+
# Convert to list -- subplots returns a single Axes but we want a list
|
1113
|
+
self.ax = [self.fig.subplots(self.nbrows, self.nbcols)]
|
1114
|
+
else:
|
1115
|
+
# Flatten the axes -- sbplots returns a 2D array of Axes but we want a list
|
1116
|
+
self.ax = self.fig.subplots(self.nbrows, self.nbcols).flatten()
|
1117
|
+
|
1118
|
+
# store the axes in a dict -- So we can access them by name, not only by index
|
1119
|
+
self.ax_dict = {f'{i}':ax for i, ax in enumerate(self.ax)}
|
1120
|
+
for key,ax in self.ax_dict.items():
|
1121
|
+
ax._label = key
|
1122
|
+
|
1123
|
+
elif isinstance(layout, dict):
|
1124
|
+
# dict --> Gridspec
|
1125
|
+
|
1126
|
+
# Check if nrows and ncols are defined
|
1127
|
+
if 'nrows' not in layout or 'ncols' not in layout:
|
1128
|
+
logging.warning('nrows and ncols must be defined in the layout')
|
1129
|
+
return
|
1130
|
+
|
1131
|
+
if 'ax_cells' not in layout:
|
1132
|
+
logging.warning('ax_cells must be defined in the layout')
|
1133
|
+
return
|
1134
|
+
|
1135
|
+
gs:GridSpec = self.fig.add_gridspec(nrows= layout['nrows'], ncols= layout['ncols'])
|
1136
|
+
ax_cells = layout['ax_cells']
|
1137
|
+
|
1138
|
+
for row_start, row_end, col_start, col_end, key in ax_cells:
|
1139
|
+
self.ax_dict[key] = self.fig.add_subplot(gs[row_start:row_end, col_start:col_end])
|
1140
|
+
self.ax_dict[key]._label = key
|
1141
|
+
|
1142
|
+
self.ax = [ax for ax in self.ax_dict.values()]
|
1143
|
+
|
1144
|
+
self._fig_properties = Matplotlib_figure_properties(self, self.fig)
|
1145
|
+
|
1146
|
+
if self.wx_exists:
|
1147
|
+
self.set_wx()
|
1148
|
+
|
1149
|
+
@property
|
1150
|
+
def fig_properties(self) -> "Matplotlib_figure_properties":
|
1151
|
+
return self._fig_properties
|
1152
|
+
|
1153
|
+
@property
|
1154
|
+
def _axes_properties(self) -> list[Matplotlib_ax_properties]:
|
1155
|
+
return self._fig_properties._axes
|
1156
|
+
|
1157
|
+
@property
|
1158
|
+
def nbrows(self):
|
1159
|
+
return self._nbrows
|
1160
|
+
|
1161
|
+
@nbrows.setter
|
1162
|
+
def nbrows(self, value:int):
|
1163
|
+
self._nbrows = value
|
1164
|
+
|
1165
|
+
@property
|
1166
|
+
def nbcols(self):
|
1167
|
+
return self._nbcols
|
1168
|
+
|
1169
|
+
@nbcols.setter
|
1170
|
+
def nbcols(self, value:int):
|
1171
|
+
self._nbcols = value
|
1172
|
+
|
1173
|
+
@property
|
1174
|
+
def nb_axes(self):
|
1175
|
+
return len(self.ax)
|
1176
|
+
|
1177
|
+
def set_wx(self):
|
1178
|
+
""" Set the wx Frame Design """
|
1179
|
+
|
1180
|
+
self.SetIcon(wx.Icon(str(Path(__file__).parent / "apps/wolf_logo2.bmp")))
|
1181
|
+
|
1182
|
+
self._sizer = wx.BoxSizer(wx.VERTICAL)
|
1183
|
+
|
1184
|
+
# Matplotlib canvas interacting with wx
|
1185
|
+
# --------------------------------------
|
1186
|
+
|
1187
|
+
self._canvas = FigureCanvas(self, -1, self.fig)
|
1188
|
+
self._sizer.Add(self._canvas, 1, wx.EXPAND | wx.ALL)
|
1189
|
+
|
1190
|
+
# Bind events
|
1191
|
+
self._canvas.Bind(wx.EVT_ENTER_WINDOW, self.ChangeCursor)
|
1192
|
+
self._canvas.mpl_connect('motion_notify_event', self.UpdateStatusBar)
|
1193
|
+
self._canvas.mpl_connect('button_press_event', self.OnClickCanvas)
|
1194
|
+
self._canvas.mpl_connect('key_press_event', self.OnKeyCanvas)
|
1195
|
+
|
1196
|
+
# Toolbar - Matplotlib
|
1197
|
+
# --------------------
|
1198
|
+
|
1199
|
+
self._toolbar = NavigationToolbar(self._canvas, self)
|
1200
|
+
|
1201
|
+
|
1202
|
+
# Buttons - Figure, Axes, Lines properties
|
1203
|
+
# --------- ------------------------------
|
1204
|
+
|
1205
|
+
self._prop_but = wx.Button(self, -1, 'Figure Properties')
|
1206
|
+
|
1207
|
+
self._ax_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
1208
|
+
self._ax_current = wx.Choice(self, -1, choices=[ax._label for ax in self.ax])
|
1209
|
+
self._ax_current.SetToolTip('Select the current ax -- Axes are enumerated from left to right and top to bottom')
|
1210
|
+
self._ax_current.SetSelection(0)
|
1211
|
+
self._ax_but = wx.Button(self, -1, 'Ax Properties')
|
1212
|
+
self._ax_but.SetToolTip('Choosing the properties of the current ax -- Axes are enumerated from left to right and top to bottom')
|
1213
|
+
|
1214
|
+
self._ax_current.Bind(wx.EVT_CHOICE, self.on_ax_choice)
|
1215
|
+
self._ax_but.Bind(wx.EVT_BUTTON, self.on_ax_properties)
|
1216
|
+
|
1217
|
+
self._ax_sizer.Add(self._ax_current, 1, wx.EXPAND)
|
1218
|
+
self._ax_sizer.Add(self._ax_but, 1, wx.EXPAND)
|
1219
|
+
|
1220
|
+
self._line_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
1221
|
+
self._line_current = wx.Choice(self, -1, choices=[str(i) for i in range(len(self.cur_ax.get_lines()))])
|
1222
|
+
self._line_current.SetSelection(0)
|
1223
|
+
self._line_but = wx.Button(self, -1, 'Line Properties')
|
1224
|
+
|
1225
|
+
self._line_but.Bind(wx.EVT_BUTTON, self.on_line_properties)
|
1226
|
+
self._line_current.Bind(wx.EVT_CHOICE, self.on_line_choose)
|
1227
|
+
|
1228
|
+
self._line_sizer.Add(self._line_current, 1, wx.EXPAND)
|
1229
|
+
self._line_sizer.Add(self._line_but, 1, wx.EXPAND)
|
1230
|
+
|
1231
|
+
self.Bind(wx.EVT_CLOSE, self.on_close)
|
1232
|
+
self._prop_but.Bind(wx.EVT_BUTTON, self.on_fig_properties)
|
1233
|
+
|
1234
|
+
self._sizer.Add(self._toolbar, 0, wx.EXPAND)
|
1235
|
+
self._sizer.Add(self._prop_but, 0, wx.EXPAND)
|
1236
|
+
|
1237
|
+
self._sizer.Add(self._ax_sizer, 0, wx.EXPAND)
|
1238
|
+
self._sizer.Add(self._line_sizer, 0, wx.EXPAND)
|
1239
|
+
|
1240
|
+
self._statusbar = wx.StatusBar(self)
|
1241
|
+
self._sizer.Add(self._statusbar, 0, wx.EXPAND)
|
1242
|
+
|
1243
|
+
# Buttons - Save, Load
|
1244
|
+
# --------------------
|
1245
|
+
|
1246
|
+
self._save_but = wx.Button(self, -1, 'Save')
|
1247
|
+
self._load_but = wx.Button(self, -1, 'Load')
|
1248
|
+
|
1249
|
+
self._save_but.Bind(wx.EVT_BUTTON, self.on_save)
|
1250
|
+
self._load_but.Bind(wx.EVT_BUTTON, self.on_load)
|
1251
|
+
|
1252
|
+
self._sizer_save_load = wx.BoxSizer(wx.HORIZONTAL)
|
1253
|
+
self._sizer_save_load.Add(self._save_but, 1, wx.EXPAND)
|
1254
|
+
self._sizer_save_load.Add(self._load_but, 1, wx.EXPAND)
|
1255
|
+
self._sizer.Add(self._sizer_save_load, 0, wx.EXPAND)
|
1256
|
+
|
1257
|
+
self._applyt_but = wx.Button(self, -1, 'Apply Properties')
|
1258
|
+
self._applyt_but.Bind(wx.EVT_BUTTON, self.onapply_properties)
|
1259
|
+
self._sizer.Add(self._applyt_but, 0, wx.EXPAND)
|
1260
|
+
|
1261
|
+
# Collapsible pane -- Grid Xls, Properties
|
1262
|
+
# ----------------------------------
|
1263
|
+
|
1264
|
+
self._collaps_pane = wx.CollapsiblePane(self, label='Properties', style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE)
|
1265
|
+
self._collaps_pane.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.on_collaps_pane)
|
1266
|
+
|
1267
|
+
win = self._collaps_pane.GetPane()
|
1268
|
+
self._sizer_grid_props = wx.BoxSizer(wx.HORIZONTAL)
|
1269
|
+
win.SetSizer(self._sizer_grid_props)
|
1270
|
+
self._sizer_grid_props.SetSizeHints(win)
|
1271
|
+
|
1272
|
+
# XLS sizer
|
1273
|
+
# ---------
|
1274
|
+
self._sizer_xls = wx.BoxSizer(wx.VERTICAL)
|
1275
|
+
|
1276
|
+
self._xls = CpGrid(win, -1, wx.WANTS_CHARS)
|
1277
|
+
self._update_xy = wx.Button(win, -1, 'Update XY')
|
1278
|
+
self._update_xy.Bind(wx.EVT_BUTTON, self.update_line_from_grid)
|
1279
|
+
|
1280
|
+
self._add_row = wx.Button(win, -1, 'Add rows')
|
1281
|
+
self._add_row.Bind(wx.EVT_BUTTON, self.add_row_to_grid)
|
1282
|
+
|
1283
|
+
self._add_line = wx.Button(win, -1, 'Add line')
|
1284
|
+
self._add_line.Bind(wx.EVT_BUTTON, self.onadd_line)
|
1285
|
+
|
1286
|
+
self._del_line = wx.Button(win, -1, 'Remove line')
|
1287
|
+
self._del_line.Bind(wx.EVT_BUTTON, self.ondel_line)
|
1288
|
+
|
1289
|
+
self._sizer_xls.Add(self._xls, 1, wx.EXPAND)
|
1290
|
+
self._sizer_xls.Add(self._update_xy, 0, wx.EXPAND)
|
1291
|
+
self._sizer_xls.Add(self._add_row, 0, wx.EXPAND)
|
1292
|
+
self._sizer_xls.Add(self._add_line, 0, wx.EXPAND)
|
1293
|
+
self._sizer_xls.Add(self._del_line, 0, wx.EXPAND)
|
1294
|
+
|
1295
|
+
# Properties sizer
|
1296
|
+
# ---------------
|
1297
|
+
|
1298
|
+
# Add all props from axes
|
1299
|
+
self._fig_properties.add_props_to_sizer(win, self._sizer_grid_props)
|
1300
|
+
|
1301
|
+
self._sizer_grid_props.Add(self._sizer_xls, 1, wx.GROW | wx.ALL)
|
1302
|
+
|
1303
|
+
# self._sizer.Add(self._sizer_grid_props, 1, wx.EXPAND)
|
1304
|
+
self._collaps_pane.Expand()
|
1305
|
+
|
1306
|
+
self._sizer.Add(self._collaps_pane, 0, wx.EXPAND | wx.ALL)
|
1307
|
+
|
1308
|
+
self._xls.CreateGrid(10, 2)
|
1309
|
+
self._xls.SetColLabelValue(0, 'X')
|
1310
|
+
self._xls.SetColLabelValue(1, 'Y')
|
1311
|
+
self._xls.SetMaxSize((-1, 400))
|
1312
|
+
|
1313
|
+
|
1314
|
+
self.SetSizer(self._sizer)
|
1315
|
+
self.SetAutoLayout(True)
|
1316
|
+
# self.Layout()
|
1317
|
+
self.Fit()
|
1318
|
+
|
1319
|
+
self.Bind(wx.EVT_SIZE, self.on_size)
|
1320
|
+
|
1321
|
+
self.Show()
|
1322
|
+
self._collapsible_size = self._collaps_pane.GetSize()
|
1323
|
+
|
1324
|
+
def on_save(self, event):
|
1325
|
+
""" Save the figure """
|
1326
|
+
|
1327
|
+
with wx.FileDialog(self, "Save figure", wildcard="JSON files (*.json)|*.json", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:
|
1328
|
+
if fileDialog.ShowModal() == wx.ID_CANCEL:
|
1329
|
+
return
|
1330
|
+
|
1331
|
+
path = fileDialog.GetPath()
|
1332
|
+
self.save(str(path))
|
1333
|
+
|
1334
|
+
def on_load(self, event):
|
1335
|
+
""" Load the figure """
|
1336
|
+
|
1337
|
+
with wx.FileDialog(self, "Open figure", wildcard="JSON files (*.json)|*.json", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
|
1338
|
+
if fileDialog.ShowModal() == wx.ID_CANCEL:
|
1339
|
+
return
|
1340
|
+
|
1341
|
+
path = fileDialog.GetPath()
|
1342
|
+
self.load(str(path))
|
1343
|
+
|
1344
|
+
def ChangeCursor(self, event:MouseEvent):
|
1345
|
+
self._canvas.SetCursor(wx.Cursor(wx.CURSOR_BULLSEYE))
|
1346
|
+
|
1347
|
+
def UpdateStatusBar(self, event:MouseEvent):
|
1348
|
+
|
1349
|
+
if event.inaxes:
|
1350
|
+
idx= event.inaxes.get_figure().axes.index(event.inaxes)
|
1351
|
+
x, y = event.xdata, event.ydata
|
1352
|
+
self._statusbar.SetStatusText("Axes index= " + str(idx) + " -- x= "+str(x)+" -- y="+str(y))
|
1353
|
+
|
1354
|
+
def _mask_all_axes_props(self):
|
1355
|
+
for ax_prop in self._axes_properties:
|
1356
|
+
ax_prop._myprops.prop.Hide()
|
1357
|
+
|
1358
|
+
def _show_axes_props(self, idx:int):
|
1359
|
+
self._mask_all_axes_props()
|
1360
|
+
self._axes_properties[idx]._myprops.prop.Show()
|
1361
|
+
|
1362
|
+
@property
|
1363
|
+
def cur_ax(self) -> Axes:
|
1364
|
+
return self.ax[int(self._ax_current.GetSelection())]
|
1365
|
+
|
1366
|
+
@cur_ax.setter
|
1367
|
+
def cur_ax(self, idx:int):
|
1368
|
+
if idx < 0 or idx >= len(self.ax):
|
1369
|
+
logging.warning('Index out of range')
|
1370
|
+
return
|
1371
|
+
|
1372
|
+
self._ax_current.SetSelection(idx)
|
1373
|
+
self._fill_lines_ax()
|
1374
|
+
|
1375
|
+
@property
|
1376
|
+
def cur_ax_properties(self) -> Matplotlib_ax_properties:
|
1377
|
+
return self._axes_properties[int(self._ax_current.GetSelection())]
|
1378
|
+
|
1379
|
+
@cur_ax_properties.setter
|
1380
|
+
def cur_ax_properties(self, idx:int):
|
1381
|
+
|
1382
|
+
if idx < 0 or idx >= len(self._axes_properties):
|
1383
|
+
logging.warning('Index out of range')
|
1384
|
+
return
|
1385
|
+
|
1386
|
+
self._ax_current.SetSelection(idx)
|
1387
|
+
self._fill_lines_ax()
|
1388
|
+
|
1389
|
+
@property
|
1390
|
+
def cur_line_properties(self) -> Matplolib_line_properties:
|
1391
|
+
|
1392
|
+
if self._line_current.GetSelection() == -1:
|
1393
|
+
return None
|
1394
|
+
|
1395
|
+
return self.cur_ax_properties._lines[int(self._line_current.GetSelection())]
|
1396
|
+
|
1397
|
+
@property
|
1398
|
+
def cur_line(self) -> Line2D:
|
1399
|
+
return self.cur_ax.get_lines()[int(self._line_current.GetSelection())]
|
1400
|
+
|
1401
|
+
def get_figax(self):
|
1402
|
+
|
1403
|
+
if len(self.ax) == 1:
|
1404
|
+
return self.fig, self.ax[0]
|
1405
|
+
else:
|
1406
|
+
return self.fig, self.ax
|
1407
|
+
|
1408
|
+
def on_close(self, event):
|
1409
|
+
self.Destroy()
|
1410
|
+
|
1411
|
+
def on_fig_properties(self, event):
|
1412
|
+
""" Show the figure properties """
|
1413
|
+
self.show_fig_properties()
|
1414
|
+
|
1415
|
+
def show_fig_properties(self):
|
1416
|
+
# self._fig_properties.ui()
|
1417
|
+
self._hide_all_props()
|
1418
|
+
self._fig_properties.show_props()
|
1419
|
+
self.Layout()
|
1420
|
+
self.shown_props = self._fig_properties
|
1421
|
+
|
1422
|
+
def _fill_lines_ax(self, idx:int = None):
|
1423
|
+
self._line_current.SetItems([line.get_label() for line in self.cur_ax.get_lines()])
|
1424
|
+
self._line_current.SetSelection(0)
|
1425
|
+
|
1426
|
+
if idx is not None:
|
1427
|
+
self._line_current.SetSelection(idx)
|
1428
|
+
|
1429
|
+
def on_ax_choice(self, event):
|
1430
|
+
self._fill_lines_ax()
|
1431
|
+
|
1432
|
+
def on_ax_properties(self, event):
|
1433
|
+
""" Show the ax properties """
|
1434
|
+
self.show_curax_properties()
|
1435
|
+
|
1436
|
+
def show_curax_properties(self):
|
1437
|
+
# self.cur_ax_properties.ui()
|
1438
|
+
self._hide_all_props()
|
1439
|
+
self.cur_ax_properties.show_props()
|
1440
|
+
self.Layout()
|
1441
|
+
self.shown_props = self.cur_ax_properties
|
1442
|
+
|
1443
|
+
def on_line_properties(self, event):
|
1444
|
+
""" Show the line properties """
|
1445
|
+
self.show_curline_properties()
|
1446
|
+
|
1447
|
+
def show_curline_properties(self):
|
1448
|
+
# self.cur_line_properties.ui()
|
1449
|
+
self._hide_all_props()
|
1450
|
+
self.cur_line_properties.show_props()
|
1451
|
+
# self.Layout()
|
1452
|
+
self._sizer_grid_props.Layout()
|
1453
|
+
self.shown_props = self.cur_line_properties
|
1454
|
+
|
1455
|
+
def onapply_properties(self, event):
|
1456
|
+
""" Apply the properties """
|
1457
|
+
|
1458
|
+
if self.shown_props is not None:
|
1459
|
+
self.shown_props.fill_property()
|
1460
|
+
self.update_layout()
|
1461
|
+
|
1462
|
+
def _hide_all_props(self):
|
1463
|
+
|
1464
|
+
self._fig_properties.hide_props()
|
1465
|
+
for ax_prop in self._axes_properties:
|
1466
|
+
ax_prop.hide_all_props()
|
1467
|
+
|
1468
|
+
def on_line_choose(self, event):
|
1469
|
+
|
1470
|
+
self.cur_ax_properties.reset_selection()
|
1471
|
+
self.cur_ax_properties.select_line(self._line_current.GetSelection())
|
1472
|
+
self.fill_grid_with_xy()
|
1473
|
+
|
1474
|
+
def on_size(self, event):
|
1475
|
+
""" Resize event """
|
1476
|
+
|
1477
|
+
width, height = self.fig.get_size_inches()
|
1478
|
+
dpi = self.fig.get_dpi()
|
1479
|
+
width_pix = int(width * dpi)
|
1480
|
+
height_pix = int(height * dpi)
|
1481
|
+
|
1482
|
+
self._canvas.MinSize = (width_pix, height_pix)
|
1483
|
+
|
1484
|
+
self._collapsible_size = self._collaps_pane.GetSize()
|
1485
|
+
|
1486
|
+
event.Skip()
|
1487
|
+
|
1488
|
+
def update_layout(self):
|
1489
|
+
|
1490
|
+
if not self.wx_exists:
|
1491
|
+
return
|
1492
|
+
|
1493
|
+
width, height = self.fig.get_size_inches()
|
1494
|
+
dpi = self.fig.get_dpi()
|
1495
|
+
width_pix = int(width * dpi)
|
1496
|
+
height_pix = int(height * dpi)
|
1497
|
+
|
1498
|
+
self._canvas.MinSize = (width_pix, height_pix)
|
1499
|
+
|
1500
|
+
self.SetSize((width_pix + 16, height_pix + 210 + self._collapsible_size[1]))
|
1501
|
+
|
1502
|
+
self.Fit()
|
1503
|
+
|
1504
|
+
def on_collaps_pane(self, event):
|
1505
|
+
""" Collapsible pane event """
|
1506
|
+
|
1507
|
+
if event.GetCollapsed():
|
1508
|
+
self._collaps_pane.Collapse()
|
1509
|
+
else:
|
1510
|
+
self._collaps_pane.Expand()
|
1511
|
+
|
1512
|
+
if self._collapsible_size != self._collaps_pane.GetSize():
|
1513
|
+
self.SetSize((self.GetSize()[0], self.GetSize()[1] + self._collaps_pane.GetSize()[1]-self._collapsible_size[1]))
|
1514
|
+
self._collapsible_size = self._collaps_pane.GetSize()
|
1515
|
+
|
1516
|
+
self.Fit()
|
1517
|
+
|
1518
|
+
def OnKeyCanvas(self, event:KeyEvent):
|
1519
|
+
|
1520
|
+
if event.key == 'escape':
|
1521
|
+
self._axes_properties[int(self._ax_current.GetSelection())].reset_selection()
|
1522
|
+
|
1523
|
+
def OnClickCanvas(self, event:MouseEvent):
|
1524
|
+
|
1525
|
+
rclick = event.button == 3
|
1526
|
+
lclick = event.button == 1
|
1527
|
+
|
1528
|
+
if not rclick:
|
1529
|
+
return
|
1530
|
+
|
1531
|
+
if event.inaxes:
|
1532
|
+
ax:Axes = event.inaxes
|
1533
|
+
idx= ax.get_figure().axes.index(event.inaxes)
|
1534
|
+
x, y = event.xdata, event.ydata
|
1535
|
+
|
1536
|
+
dist_min = 1e6
|
1537
|
+
line_min = None
|
1538
|
+
|
1539
|
+
for line in ax.get_lines():
|
1540
|
+
xy = line.get_xydata()
|
1541
|
+
dist = np.linalg.norm(xy - np.array([x,y]), axis=1)
|
1542
|
+
idx_min = np.argmin(dist)
|
1543
|
+
if dist[idx_min] < dist_min:
|
1544
|
+
dist_min = dist[idx_min]
|
1545
|
+
line_min = line
|
1546
|
+
|
1547
|
+
self._ax_current.SetSelection(idx)
|
1548
|
+
self._fill_lines_ax(idx = ax.get_lines().index(line_min))
|
1549
|
+
self._axes_properties[idx].select_line(ax.get_lines().index(line_min))
|
1550
|
+
self.fill_grid_with_xy(line_min)
|
1551
|
+
|
1552
|
+
self.show_curline_properties()
|
1553
|
+
|
1554
|
+
def fill_grid_with_xy(self, line:Line2D= None, grid:CpGrid= None, colx:int= 0, coly:int= 1):
|
1555
|
+
|
1556
|
+
if line is None:
|
1557
|
+
line = self.cur_line
|
1558
|
+
|
1559
|
+
if grid is None:
|
1560
|
+
grid = self._xls
|
1561
|
+
|
1562
|
+
xy = line.get_xydata()
|
1563
|
+
|
1564
|
+
grid.ClearGrid()
|
1565
|
+
|
1566
|
+
if grid.GetNumberRows() < len(xy):
|
1567
|
+
grid.AppendRows(len(xy)-grid.GetNumberRows())
|
1568
|
+
elif grid.GetNumberRows() > len(xy):
|
1569
|
+
grid.DeleteRows(len(xy), grid.GetNumberRows()-len(xy))
|
1570
|
+
|
1571
|
+
for i in range(len(xy)):
|
1572
|
+
grid.SetCellValue(i, colx, str(xy[i,0]))
|
1573
|
+
grid.SetCellValue(i, coly, str(xy[i,1]))
|
1574
|
+
|
1575
|
+
def update_line_from_grid(self, event):
|
1576
|
+
|
1577
|
+
line = self.cur_line
|
1578
|
+
|
1579
|
+
#count not null values
|
1580
|
+
n = 0
|
1581
|
+
for i in range(self._xls.GetNumberRows()):
|
1582
|
+
if self._xls.GetCellValue(i, 0) != '' and self._xls.GetCellValue(i, 1) != '':
|
1583
|
+
n += 1
|
1584
|
+
|
1585
|
+
xy = np.zeros((n, 2))
|
1586
|
+
|
1587
|
+
for i in range(n):
|
1588
|
+
xy[i,0] = float(self._xls.GetCellValue(i, 0))
|
1589
|
+
xy[i,1] = float(self._xls.GetCellValue(i, 1))
|
1590
|
+
|
1591
|
+
line.set_data(xy[:,0], xy[:,1])
|
1592
|
+
self.update_layout()
|
1593
|
+
|
1594
|
+
def add_row_to_grid(self, event):
|
1595
|
+
|
1596
|
+
dlg = wx.TextEntryDialog(self, 'Number of rows to add', 'Add rows', '1')
|
1597
|
+
dlg.ShowModal()
|
1598
|
+
|
1599
|
+
try:
|
1600
|
+
n = int(dlg.GetValue())
|
1601
|
+
except:
|
1602
|
+
n = 1
|
1603
|
+
|
1604
|
+
self._xls.AppendRows(n)
|
1605
|
+
|
1606
|
+
def onadd_line(self, event):
|
1607
|
+
""" Add a plot to the current ax """
|
1608
|
+
|
1609
|
+
xy = self._get_xy_from_grid(self._xls)
|
1610
|
+
self.add_line(xy, self.cur_ax)
|
1611
|
+
|
1612
|
+
def _get_xy_from_grid(self, grid:CpGrid, colx:int= 0, coly:int= 1):
|
1613
|
+
""" Get the xy from a grid """
|
1614
|
+
|
1615
|
+
#Searching xy in the grid
|
1616
|
+
#count not null values
|
1617
|
+
n = 0
|
1618
|
+
for i in range(grid.GetNumberRows()):
|
1619
|
+
if grid.GetCellValue(i, colx) != '' and grid.GetCellValue(i, coly) != '':
|
1620
|
+
n += 1
|
1621
|
+
|
1622
|
+
xy = np.zeros((n, 2))
|
1623
|
+
|
1624
|
+
for i in range(n):
|
1625
|
+
xy[i,0] = float(grid.GetCellValue(i, colx))
|
1626
|
+
xy[i,1] = float(grid.GetCellValue(i, coly))
|
1627
|
+
|
1628
|
+
return xy
|
1629
|
+
|
1630
|
+
def add_line(self, xy:np.ndarray, ax:Axes=None, **kwargs):
|
1631
|
+
""" Add a plot to the current ax """
|
1632
|
+
|
1633
|
+
ax, idx_ax = self.get_ax_idx(ax)
|
1634
|
+
|
1635
|
+
ax.plot(xy[:,0], xy[:,1], **kwargs)
|
1636
|
+
|
1637
|
+
cur_ax_prop:Matplotlib_ax_properties = self._axes_properties[idx_ax]
|
1638
|
+
cur_ax_prop._lines.append(Matplolib_line_properties(ax.get_lines()[-1], cur_ax_prop))
|
1639
|
+
cur_ax_prop._lines[-1].add_props_to_sizer(self._collaps_pane.GetPane(), self._sizer_grid_props)
|
1640
|
+
self.update_layout()
|
1641
|
+
|
1642
|
+
def ondel_line(self, event):
|
1643
|
+
""" Remove a plot from the current ax """
|
1644
|
+
|
1645
|
+
dlg = wx.MessageDialog(self, _('Do you want to remove the selected line?\n\nSuch action is irrevocable !\n\nPlease consider to set "Visible" to "False" to hide data'), _('Remove line'), wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT)
|
1646
|
+
|
1647
|
+
ret = dlg.ShowModal()
|
1648
|
+
if ret == wx.ID_NO:
|
1649
|
+
return
|
1650
|
+
|
1651
|
+
if self._line_current.GetSelection() == -1:
|
1652
|
+
return
|
1653
|
+
|
1654
|
+
idx = self._line_current.GetSelection()
|
1655
|
+
self.del_line(idx)
|
1656
|
+
|
1657
|
+
def del_line(self, idx:int):
|
1658
|
+
""" Delete a line """
|
1659
|
+
|
1660
|
+
self.cur_ax_properties.del_line(idx)
|
1661
|
+
self.update_layout()
|
1662
|
+
|
1663
|
+
def get_ax_idx(self, key:str | int | Axes= None) -> Axes:
|
1664
|
+
|
1665
|
+
if key is None:
|
1666
|
+
return self.cur_ax, self._ax_current.GetSelection()
|
1667
|
+
|
1668
|
+
if isinstance(key, str):
|
1669
|
+
if key in self.ax_dict:
|
1670
|
+
return self.ax_dict[key], list(self.ax_dict.keys()).index(key)
|
1671
|
+
else:
|
1672
|
+
logging.warning('Key not found')
|
1673
|
+
return None
|
1674
|
+
elif isinstance(key, int):
|
1675
|
+
if key >= 0 and key < len(self.ax):
|
1676
|
+
return self.ax[key], key
|
1677
|
+
else:
|
1678
|
+
logging.warning('Index out of range')
|
1679
|
+
return None
|
1680
|
+
elif isinstance(key, Axes):
|
1681
|
+
return key, list(self.ax_dict.values()).index(key)
|
1682
|
+
|
1683
|
+
def plot(self, x:np.ndarray, y:np.ndarray, ax:Axes | int | str= None, **kwargs):
|
1684
|
+
|
1685
|
+
ax, idx_ax = self.get_ax_idx(ax)
|
1686
|
+
|
1687
|
+
ax.plot(x, y, **kwargs)
|
1688
|
+
|
1689
|
+
new_props = Matplolib_line_properties(ax.get_lines()[-1], self._axes_properties[idx_ax])
|
1690
|
+
|
1691
|
+
if self.wx_exists:
|
1692
|
+
new_props.add_props_to_sizer(self._collaps_pane.GetPane(), self._sizer_grid_props)
|
1693
|
+
|
1694
|
+
ax_prop:Matplotlib_ax_properties = self._axes_properties[idx_ax]
|
1695
|
+
ax_prop._lines.append(new_props)
|
1696
|
+
ax_prop.get_properties()
|
1697
|
+
|
1698
|
+
if self.wx_exists:
|
1699
|
+
if ax == self.cur_ax:
|
1700
|
+
self._line_current.SetItems([line.get_label() for line in ax.get_lines()])
|
1701
|
+
self._line_current.SetSelection(len(ax.get_lines())-1)
|
1702
|
+
|
1703
|
+
self.fig.tight_layout()
|
1704
|
+
self.update_layout()
|
1705
|
+
|
1706
|
+
def to_dict(self) -> dict:
|
1707
|
+
""" properties to dict """
|
1708
|
+
|
1709
|
+
ret = {}
|
1710
|
+
if self.wx_exists:
|
1711
|
+
ret['frame_name'] = self.GetName()
|
1712
|
+
ret['frame_size_x'] = self.GetSize()[0]
|
1713
|
+
ret['frame_size_y'] = self.GetSize()[1]
|
1714
|
+
|
1715
|
+
ret['layout'] = self._layout
|
1716
|
+
ret['fig'] = self._fig_properties.to_dict()
|
1717
|
+
ret['axes'] = [ax.to_dict() for ax in self._axes_properties]
|
1718
|
+
|
1719
|
+
return ret
|
1720
|
+
|
1721
|
+
def from_dict(self, props:dict):
|
1722
|
+
""" properties from dict """
|
1723
|
+
|
1724
|
+
if 'layout' not in props:
|
1725
|
+
logging.error('No layout found in properties')
|
1726
|
+
return
|
1727
|
+
|
1728
|
+
self.apply_layout(props['layout'])
|
1729
|
+
self._fig_properties.from_dict(props['fig'])
|
1730
|
+
for ax_props, ax in zip(props['axes'], self._axes_properties):
|
1731
|
+
ax:Matplotlib_ax_properties
|
1732
|
+
ax.from_dict(ax_props)
|
1733
|
+
|
1734
|
+
|
1735
|
+
if self.wx_exists:
|
1736
|
+
for ax_props, ax in zip(props['axes'], self._axes_properties):
|
1737
|
+
for line in ax._lines:
|
1738
|
+
line.add_props_to_sizer(self._collaps_pane.GetPane(), self._sizer_grid_props)
|
1739
|
+
|
1740
|
+
if 'frame_name' in props:
|
1741
|
+
self.SetName(props['frame_name'])
|
1742
|
+
if 'frame_size_x' in props and 'frame_size_y' in props:
|
1743
|
+
self.SetSize(props['frame_size_x'], props['frame_size_y'])
|
1744
|
+
|
1745
|
+
self.Layout()
|
1746
|
+
|
1747
|
+
return self
|
1748
|
+
|
1749
|
+
def serialize(self):
|
1750
|
+
""" Serialize the properties """
|
1751
|
+
|
1752
|
+
return json.dumps(self.to_dict(), indent=4)
|
1753
|
+
|
1754
|
+
def deserialize(self, props:str):
|
1755
|
+
""" Deserialize the properties """
|
1756
|
+
|
1757
|
+
self.from_dict(json.loads(props))
|
1758
|
+
|
1759
|
+
def save(self, filename:str):
|
1760
|
+
|
1761
|
+
with open(filename, 'w') as f:
|
1762
|
+
f.write(self.serialize())
|
1763
|
+
|
1764
|
+
def load(self, filename:str):
|
1765
|
+
|
1766
|
+
with open(filename, 'r') as f:
|
1767
|
+
self.deserialize(f.read())
|
1768
|
+
|
1769
|
+
def save_image(self, filename:str, dpi:int= 100):
|
1770
|
+
|
1771
|
+
self.fig.savefig(filename, dpi= dpi)
|
1772
|
+
|
1773
|
+
def set_x_bounds(self, xmin:float, xmax:float, ax:Axes | int | str= None):
|
1774
|
+
|
1775
|
+
ax, idx_ax = self.get_ax_idx(ax)
|
1776
|
+
|
1777
|
+
ax.set_xlim(xmin, xmax)
|
1778
|
+
self._axes_properties[idx_ax].get_properties()
|
1779
|
+
|
1780
|
+
self.fig.tight_layout()
|
1781
|
+
self._canvas.draw()
|
1782
|
+
|
1783
|
+
def set_y_bounds(self, ymin:float, ymax:float, ax:Axes | int | str= None):
|
1784
|
+
|
1785
|
+
ax, idx_ax = self.get_ax_idx(ax)
|
1786
|
+
|
1787
|
+
ax.set_ylim(ymin, ymax)
|
1788
|
+
self._axes_properties[idx_ax].get_properties()
|
1789
|
+
|
1790
|
+
self.fig.tight_layout()
|
1791
|
+
self._canvas.draw()
|
1792
|
+
class Matplotlib_figure_properties():
|
1793
|
+
|
1794
|
+
def __init__(self, parent:Matplotlib_Figure = None, fig:Figure = None) -> None:
|
1795
|
+
|
1796
|
+
self.wx_exists = wx.App.Get() is not None
|
1797
|
+
|
1798
|
+
self.parent = parent
|
1799
|
+
self._myprops = None
|
1800
|
+
self._fig:Figure = None
|
1801
|
+
self._axes = None
|
1802
|
+
|
1803
|
+
self.title = 'Figure'
|
1804
|
+
self.size_width = 8
|
1805
|
+
self.size_height = 6
|
1806
|
+
self.dpi = 100
|
1807
|
+
self._filename = None
|
1808
|
+
|
1809
|
+
self.set_fig(fig)
|
1810
|
+
self._set_props()
|
1811
|
+
|
1812
|
+
def set_fig(self, fig:Figure):
|
1813
|
+
|
1814
|
+
self._fig = fig
|
1815
|
+
|
1816
|
+
if fig is None:
|
1817
|
+
return
|
1818
|
+
|
1819
|
+
self._axes:list[Matplotlib_ax_properties] = [Matplotlib_ax_properties(ax) for ax in fig.get_axes()]
|
1820
|
+
self.get_properties()
|
1821
|
+
|
1822
|
+
return self
|
1823
|
+
|
1824
|
+
def _set_props(self):
|
1825
|
+
""" Set the properties UI """
|
1826
|
+
|
1827
|
+
if self._myprops is not None:
|
1828
|
+
return
|
1829
|
+
|
1830
|
+
self._myprops = Wolf_Param(title='Figure properties', w= 500, h= 400, to_read= False, ontop= False, init_GUI= False)
|
1831
|
+
|
1832
|
+
self._myprops.set_callbacks(None, self.destroyprop)
|
1833
|
+
|
1834
|
+
# self._myprops.hide_selected_buttons()
|
1835
|
+
|
1836
|
+
self._myprops.addparam('Draw','Title',self.title,'String','SupTitle of the figure')
|
1837
|
+
self._myprops.addparam('Draw','Width',self.size_width,'Float','Width in inches')
|
1838
|
+
self._myprops.addparam('Draw','Height',self.size_height,'Float','Height in inches')
|
1839
|
+
self._myprops.addparam('Draw','DPI',self.dpi,'Integer','DPI - Dots per inch')
|
1840
|
+
self._myprops.addparam('Draw','Filename',self._filename,'File','Filename')
|
1841
|
+
|
1842
|
+
self._myprops.Populate()
|
1843
|
+
|
1844
|
+
# self._myprops.Layout()
|
1845
|
+
# self._myprops.SetSizeHints(500,500)
|
1846
|
+
|
1847
|
+
def populate(self):
|
1848
|
+
""" Populate the properties UI """
|
1849
|
+
|
1850
|
+
if self._myprops is None:
|
1851
|
+
self._set_props()
|
1852
|
+
|
1853
|
+
self._myprops[('Draw','Title')] = self.title
|
1854
|
+
self._myprops[('Draw','Width')] = self.size_width
|
1855
|
+
self._myprops[('Draw','Height')] = self.size_height
|
1856
|
+
self._myprops[('Draw','DPI')] = self.dpi
|
1857
|
+
self._myprops[('Draw','Filename')] = self._filename
|
1858
|
+
|
1859
|
+
self._myprops.Populate()
|
1860
|
+
|
1861
|
+
def ui(self):
|
1862
|
+
""" Create the properties UI """
|
1863
|
+
|
1864
|
+
if not self.wx_exists:
|
1865
|
+
return
|
1866
|
+
|
1867
|
+
if self._myprops is not None:
|
1868
|
+
self._myprops.CenterOnScreen()
|
1869
|
+
self._myprops.Raise()
|
1870
|
+
self._myprops.Show()
|
1871
|
+
return
|
1872
|
+
|
1873
|
+
self._set_props()
|
1874
|
+
self._myprops.Show()
|
1875
|
+
|
1876
|
+
self._myprops.SetTitle(_('Figure properties'))
|
1877
|
+
|
1878
|
+
icon = wx.Icon()
|
1879
|
+
icon_path = Path(__file__).parent / "apps/wolf_logo2.bmp"
|
1880
|
+
icon.CopyFromBitmap(wx.Bitmap(str(icon_path), wx.BITMAP_TYPE_ANY))
|
1881
|
+
self._myprops.SetIcon(icon)
|
1882
|
+
|
1883
|
+
self._myprops.Center()
|
1884
|
+
|
1885
|
+
self._myprops.Raise()
|
1886
|
+
|
1887
|
+
def destroyprop(self):
|
1888
|
+
self._myprops=None
|
1889
|
+
|
1890
|
+
def fill_property(self):
|
1891
|
+
|
1892
|
+
if self._myprops is None:
|
1893
|
+
logging.warning('Properties UI not found')
|
1894
|
+
return
|
1895
|
+
|
1896
|
+
self._myprops.apply_changes_to_memory()
|
1897
|
+
|
1898
|
+
self.title = self._myprops[('Draw','Title')]
|
1899
|
+
self.size_width = self._myprops[('Draw','Width')]
|
1900
|
+
self.size_height = self._myprops[('Draw','Height')]
|
1901
|
+
self.dpi = self._myprops[('Draw','DPI')]
|
1902
|
+
self._filename = self._myprops[('Draw','Filename')]
|
1903
|
+
|
1904
|
+
self.set_properties()
|
1905
|
+
|
1906
|
+
def set_properties(self, fig:Figure = None):
|
1907
|
+
|
1908
|
+
if fig is None:
|
1909
|
+
fig = self._fig
|
1910
|
+
|
1911
|
+
if self.size_height == 0 or self.size_width == 0:
|
1912
|
+
logging.warning('Size is 0')
|
1913
|
+
return
|
1914
|
+
|
1915
|
+
fig.set_dpi(self.dpi)
|
1916
|
+
fig.set_size_inches(self.size_width, self.size_height)
|
1917
|
+
fig.suptitle(self.title)
|
1918
|
+
fig.tight_layout()
|
1919
|
+
|
1920
|
+
fig.canvas.draw()
|
1921
|
+
|
1922
|
+
self.get_properties()
|
1923
|
+
|
1924
|
+
def get_properties(self, fig:Figure = None):
|
1925
|
+
|
1926
|
+
if fig is None:
|
1927
|
+
fig = self._fig
|
1928
|
+
|
1929
|
+
self.title = ''
|
1930
|
+
self.size_width, self.size_height = fig.get_size_inches()
|
1931
|
+
self.dpi = fig.get_dpi()
|
1932
|
+
|
1933
|
+
def to_dict(self) -> str:
|
1934
|
+
""" properties to dict """
|
1935
|
+
|
1936
|
+
return {'title':self.title if self.title != 'Figure' else '',
|
1937
|
+
'size_width':self.size_width,
|
1938
|
+
'size_height':self.size_height,
|
1939
|
+
'dpi':self.dpi}
|
1940
|
+
|
1941
|
+
def from_dict(self, props:dict):
|
1942
|
+
""" properties from dict """
|
1943
|
+
|
1944
|
+
keys = ['title', 'size_width', 'size_height', 'dpi']
|
1945
|
+
|
1946
|
+
for key in keys:
|
1947
|
+
try:
|
1948
|
+
setattr(self, key, props[key])
|
1949
|
+
except:
|
1950
|
+
logging.warning('Key not found in properties dict')
|
1951
|
+
pass
|
1952
|
+
|
1953
|
+
self.set_properties()
|
1954
|
+
|
1955
|
+
return self
|
1956
|
+
|
1957
|
+
def add_props_to_sizer(self, frame:wx.Frame, sizer:wx.BoxSizer):
|
1958
|
+
""" Add the properties to a sizer """
|
1959
|
+
|
1960
|
+
self._myprops.ensure_prop(frame, show_in_active_if_default=True, height=300)
|
1961
|
+
sizer.Add(self._myprops.prop, proportion= 1, flag= wx.EXPAND)
|
1962
|
+
self._myprops.prop.Hide()
|
1963
|
+
|
1964
|
+
for ax in self._axes:
|
1965
|
+
ax.add_props_to_sizer(frame, sizer)
|
1966
|
+
|
1967
|
+
pass
|
1968
|
+
|
1969
|
+
def show_props(self):
|
1970
|
+
""" Show the properties """
|
1971
|
+
|
1972
|
+
self._myprops.prop.Show()
|
1973
|
+
|
1974
|
+
def hide_props(self):
|
1975
|
+
""" Hide the properties """
|
1976
|
+
|
1977
|
+
self._myprops.prop.Hide()
|
1978
|
+
|
1979
|
+
|
1980
|
+
COLORS_MPL = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w', 'blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black', 'white', 'orange']
|