wolfhece 2.1.108__py3-none-any.whl → 2.1.110__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 +234 -5
- wolfhece/PyVertex.py +17 -1
- wolfhece/PyVertexvectors.py +732 -112
- wolfhece/apps/curvedigitizer.py +197 -141
- wolfhece/apps/version.py +1 -1
- wolfhece/lazviewer/laz_viewer.py +22 -0
- wolfhece/matplotlib_fig.py +433 -66
- wolfhece/pybridges.py +227 -87
- {wolfhece-2.1.108.dist-info → wolfhece-2.1.110.dist-info}/METADATA +1 -1
- {wolfhece-2.1.108.dist-info → wolfhece-2.1.110.dist-info}/RECORD +13 -13
- {wolfhece-2.1.108.dist-info → wolfhece-2.1.110.dist-info}/WHEEL +0 -0
- {wolfhece-2.1.108.dist-info → wolfhece-2.1.110.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.108.dist-info → wolfhece-2.1.110.dist-info}/top_level.txt +0 -0
wolfhece/matplotlib_fig.py
CHANGED
@@ -2,6 +2,7 @@ from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavigationToo
|
|
2
2
|
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
|
3
3
|
from typing import Literal
|
4
4
|
from matplotlib.figure import Figure
|
5
|
+
from matplotlib.axes import Axes
|
5
6
|
import matplotlib.pyplot as plt
|
6
7
|
from matplotlib.gridspec import GridSpec
|
7
8
|
import numpy as np
|
@@ -11,6 +12,8 @@ from wolfhece.PyParams import Wolf_Param, new_json
|
|
11
12
|
from wolfhece.PyTranslate import _
|
12
13
|
from wolfhece.PyVertex import getRGBfromI
|
13
14
|
|
15
|
+
from PIL import Image, ImageOps
|
16
|
+
|
14
17
|
|
15
18
|
import wx
|
16
19
|
from matplotlib.backend_bases import KeyEvent, MouseEvent
|
@@ -55,7 +58,7 @@ def sanitize_fmt(fmt):
|
|
55
58
|
|
56
59
|
class Matplotlib_ax_properties():
|
57
60
|
|
58
|
-
def __init__(self, ax =None) -> None:
|
61
|
+
def __init__(self, ax:Axes =None) -> None:
|
59
62
|
|
60
63
|
self._ax = ax
|
61
64
|
self._myprops = None
|
@@ -64,29 +67,55 @@ class Matplotlib_ax_properties():
|
|
64
67
|
self._tmp_line_prop:Matplolib_line_properties = None
|
65
68
|
self._selected_line = -1
|
66
69
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
70
|
+
if ax is None:
|
71
|
+
self.title = 'Figure'
|
72
|
+
self.xtitle = 'X [m]'
|
73
|
+
self.ytitle = 'Y [m]'
|
74
|
+
self.legend = False
|
75
|
+
self.xmin = -99999
|
76
|
+
self.xmax = -99999
|
77
|
+
self.ymin = -99999
|
78
|
+
self.ymax = -99999
|
79
|
+
self.gridx_major = False
|
80
|
+
self.gridy_major = False
|
81
|
+
self.gridx_minor = False
|
82
|
+
self.gridy_minor = False
|
83
|
+
|
84
|
+
self._equal_axis = 0
|
85
|
+
self.scaling_factor = 1.
|
86
|
+
|
87
|
+
self.ticks_x = 1.
|
88
|
+
self.ticks_y = 1.
|
89
|
+
self.ticks_label_x = 1.
|
90
|
+
self.ticks_label_y = 1.
|
91
|
+
|
92
|
+
self.format_x = '.2f'
|
93
|
+
self.format_y = '.2f'
|
94
|
+
else:
|
95
|
+
self.title = ax.get_title()
|
96
|
+
self.xtitle = ax.get_xlabel()
|
97
|
+
self.ytitle = ax.get_ylabel()
|
98
|
+
self.legend = ax.get_legend() is not None
|
99
|
+
self.xmin = -99999
|
100
|
+
self.xmax = -99999
|
101
|
+
self.ymin = -99999
|
102
|
+
self.ymax = -99999
|
103
|
+
self.gridx_major = False
|
104
|
+
self.gridy_major = False
|
105
|
+
self.gridx_minor = False
|
106
|
+
self.gridy_minor = False
|
107
|
+
|
108
|
+
aspect = ax.get_aspect()
|
109
|
+
self._equal_axis = 0 if aspect == 'auto' else 1 if aspect == 1. else 2
|
110
|
+
self.scaling_factor = aspect
|
111
|
+
|
112
|
+
self.ticks_x = ax.get_xticks()
|
113
|
+
self.ticks_y = ax.get_yticks
|
114
|
+
self.ticks_label_x = ax.get_xticklabels()
|
115
|
+
self.ticks_label_y = ax.get_yticklabels()
|
116
|
+
|
117
|
+
self.format_x = '.2f'
|
118
|
+
self.format_y = '.2f'
|
90
119
|
|
91
120
|
self._set_props()
|
92
121
|
|
@@ -234,9 +263,10 @@ class Matplotlib_ax_properties():
|
|
234
263
|
return
|
235
264
|
|
236
265
|
lines = self._ax.get_lines()
|
266
|
+
img = self._ax.get_images()
|
237
267
|
|
238
|
-
if len(lines) == 0:
|
239
|
-
logging.warning('No lines found')
|
268
|
+
if len(lines) == 0 and len(img) == 0:
|
269
|
+
logging.warning('No lines/image found')
|
240
270
|
return
|
241
271
|
|
242
272
|
xmin = np.inf
|
@@ -253,6 +283,13 @@ class Matplotlib_ax_properties():
|
|
253
283
|
ymin = min(ymin, np.min(y))
|
254
284
|
ymax = max(ymax, np.max(y))
|
255
285
|
|
286
|
+
for im in img:
|
287
|
+
x = im.get_extent()
|
288
|
+
xmin = min(xmin, x[0])
|
289
|
+
xmax = max(xmax, x[1])
|
290
|
+
ymin = min(ymin, x[2])
|
291
|
+
ymax = max(ymax, x[3])
|
292
|
+
|
256
293
|
return xmin, xmax, ymin, ymax
|
257
294
|
|
258
295
|
def fill_property(self, verbosity= True):
|
@@ -346,8 +383,10 @@ class Matplotlib_ax_properties():
|
|
346
383
|
ax.xaxis.grid(self.gridx_major)
|
347
384
|
ax.yaxis.grid(self.gridy_major)
|
348
385
|
|
349
|
-
|
350
|
-
|
386
|
+
if len(self.ticks_x) <= 100:
|
387
|
+
ax.set_xticks(self.ticks_x, self.ticks_label_x)
|
388
|
+
if len(self.ticks_y) <= 100:
|
389
|
+
ax.set_yticks(self.ticks_y, self.ticks_label_y)
|
351
390
|
|
352
391
|
if self.legend:
|
353
392
|
update = any(line.update_legend for line in self._lines)
|
@@ -612,11 +651,62 @@ class Matplolib_line_properties():
|
|
612
651
|
|
613
652
|
self.update_legend = False
|
614
653
|
|
654
|
+
self._scales = [1.0, 1.0]
|
655
|
+
self._origin_world = [0.0, 0.0]
|
656
|
+
self._origin_local = [0.0, 0.0]
|
657
|
+
|
615
658
|
self._set_props()
|
616
659
|
|
617
660
|
if self._line is not None:
|
618
661
|
self.get_properties()
|
619
662
|
|
663
|
+
def get_xydata(self, two_columns:bool = False):
|
664
|
+
""" Get the xy data """
|
665
|
+
|
666
|
+
if self._line is None:
|
667
|
+
return None
|
668
|
+
|
669
|
+
if two_columns:
|
670
|
+
return (self._line.get_xdata() - self._origin_local[0]) * self._scales[0] + self._origin_world[0], \
|
671
|
+
(self._line.get_ydata() - self._origin_local[1]) * self._scales[1] + self._origin_world[1]
|
672
|
+
else:
|
673
|
+
return np.array([(self._line.get_xdata() - self._origin_local[0]) * self._scales[0] + self._origin_world[0],
|
674
|
+
(self._line.get_ydata() - self._origin_local[1]) * self._scales[1] + self._origin_world[1]]).T
|
675
|
+
|
676
|
+
def set_xydata(self, xy_data:np.ndarray):
|
677
|
+
""" Set the xy data """
|
678
|
+
|
679
|
+
if self._line is None:
|
680
|
+
return
|
681
|
+
|
682
|
+
self._line.set_xdata((xy_data[:,0] - self._origin_world[0]) / self._scales[0] + self._origin_local[0])
|
683
|
+
self._line.set_ydata((xy_data[:,1] - self._origin_world[1]) / self._scales[1] + self._origin_local[1])
|
684
|
+
|
685
|
+
@property
|
686
|
+
def xdata(self):
|
687
|
+
return self.get_xydata(two_columns= True)[0]
|
688
|
+
|
689
|
+
@property
|
690
|
+
def ydata(self):
|
691
|
+
return self.get_xydata(two_columns= True)[1]
|
692
|
+
|
693
|
+
@property
|
694
|
+
def xydata(self):
|
695
|
+
return self.get_xydata()
|
696
|
+
|
697
|
+
@xydata.setter
|
698
|
+
def xydata(self, value):
|
699
|
+
|
700
|
+
if not isinstance(value, np.ndarray):
|
701
|
+
logging.warning('xydata must be a numpy array')
|
702
|
+
return
|
703
|
+
|
704
|
+
if value.shape[1] != 2:
|
705
|
+
logging.warning('xydata must have 2 columns')
|
706
|
+
return
|
707
|
+
|
708
|
+
self.set_xydata(value)
|
709
|
+
|
620
710
|
@property
|
621
711
|
def ax_props(self):
|
622
712
|
return self._ax_props
|
@@ -821,6 +911,14 @@ class Matplolib_line_properties():
|
|
821
911
|
self._myprops.addparam('Picker', 'Picker', self.picker, 'Logical', 'Picker')
|
822
912
|
self._myprops.addparam('Picker', 'Picker radius', self.picker_radius, 'Float', 'Picker radius')
|
823
913
|
|
914
|
+
self._myprops.addparam('Scales', 'X scale', self._scales[0], 'Float', 'X scale')
|
915
|
+
self._myprops.addparam('Scales', 'Y scale', self._scales[1], 'Float', 'Y scale')
|
916
|
+
|
917
|
+
self._myprops.addparam('Origin', 'X world', self._origin_world[0], 'Float', 'X origin into world')
|
918
|
+
self._myprops.addparam('Origin', 'Y world', self._origin_world[1], 'Float', 'Y origin into world')
|
919
|
+
self._myprops.addparam('Origin', 'X local', self._origin_local[0], 'Float', 'X origin into local references')
|
920
|
+
self._myprops.addparam('Origin', 'Y local', self._origin_local[1], 'Float', 'Y origin into local references')
|
921
|
+
|
824
922
|
self._myprops.Populate()
|
825
923
|
# self._myprops.Layout()
|
826
924
|
# self._myprops.SetSizeHints(500,500)
|
@@ -848,6 +946,14 @@ class Matplolib_line_properties():
|
|
848
946
|
self._myprops[('Picker', 'Picker')] = self.picker
|
849
947
|
self._myprops[('Picker', 'Picker radius')] = self.picker_radius
|
850
948
|
|
949
|
+
self._myprops[('Scales', 'X scale')] = self._scales[0]
|
950
|
+
self._myprops[('Scales', 'Y scale')] = self._scales[1]
|
951
|
+
|
952
|
+
self._myprops[('Origin', 'X world')] = self._origin_world[0]
|
953
|
+
self._myprops[('Origin', 'Y world')] = self._origin_world[1]
|
954
|
+
self._myprops[('Origin', 'X local')] = self._origin_local[0]
|
955
|
+
self._myprops[('Origin', 'Y local')] = self._origin_local[1]
|
956
|
+
|
851
957
|
self._myprops.Populate()
|
852
958
|
|
853
959
|
def ui(self):
|
@@ -902,6 +1008,15 @@ class Matplolib_line_properties():
|
|
902
1008
|
self.picker = self._myprops[('Picker', 'Picker')]
|
903
1009
|
self.picker_radius = self._myprops[('Picker', 'Picker radius')]
|
904
1010
|
|
1011
|
+
self._scales[0] = self._myprops[('Scales', 'X scale')]
|
1012
|
+
self._scales[1] = self._myprops[('Scales', 'Y scale')]
|
1013
|
+
|
1014
|
+
self._origin_world[0] = self._myprops[('Origin', 'X world')]
|
1015
|
+
self._origin_world[1] = self._myprops[('Origin', 'Y world')]
|
1016
|
+
|
1017
|
+
self._origin_local[0] = self._myprops[('Origin', 'X local')]
|
1018
|
+
self._origin_local[1] = self._myprops[('Origin', 'Y local')]
|
1019
|
+
|
905
1020
|
self.set_properties()
|
906
1021
|
|
907
1022
|
def set_properties(self, line:Line2D = None):
|
@@ -946,13 +1061,19 @@ class Matplolib_line_properties():
|
|
946
1061
|
def show_properties(self):
|
947
1062
|
self.ui()
|
948
1063
|
|
1064
|
+
@property
|
1065
|
+
def has_world_transfer(self):
|
1066
|
+
return self._scales[0] != 1.0 or self._scales[1] != 1.0 or self._origin_world[0] != 0. or self._origin_world[1] != 0. or self._origin_local[0] != 0. or self._origin_local[1] != 0.
|
1067
|
+
|
949
1068
|
def to_dict(self) -> str:
|
950
1069
|
""" properties to dict """
|
951
1070
|
|
952
|
-
|
953
|
-
|
1071
|
+
# We need to store the local data
|
1072
|
+
xy = self._line.get_xydata()
|
1073
|
+
xdata = xy[:,0].tolist()
|
1074
|
+
ydata = xy[:,1].tolist()
|
954
1075
|
|
955
|
-
|
1076
|
+
locdict = {'color':self.color,
|
956
1077
|
'linewidth':self.linewidth,
|
957
1078
|
'linestyle':self.linestyle,
|
958
1079
|
'marker':self.marker,
|
@@ -967,12 +1088,31 @@ class Matplolib_line_properties():
|
|
967
1088
|
'picker':self.picker,
|
968
1089
|
'picker_radius':self.picker_radius,
|
969
1090
|
'xdata':xdata,
|
970
|
-
'ydata':ydata
|
1091
|
+
'ydata':ydata,
|
1092
|
+
'xscale':self._scales[0],
|
1093
|
+
'yscale':self._scales[1],
|
1094
|
+
'xorigin_world':self._origin_world[0],
|
1095
|
+
'yorigin_world':self._origin_world[1],
|
1096
|
+
'xorigin_local':self._origin_local[0],
|
1097
|
+
'yorigin_local':self._origin_local[1]}
|
1098
|
+
|
1099
|
+
if self.has_world_transfer:
|
1100
|
+
world_xdata, world_ydata = self.get_xydata(two_columns = True)
|
1101
|
+
world_xdata = world_xdata.tolist()
|
1102
|
+
world_ydata = world_ydata.tolist()
|
1103
|
+
|
1104
|
+
locdict['world_xdata'] = world_xdata
|
1105
|
+
locdict['world_ydata'] = world_ydata
|
1106
|
+
|
1107
|
+
return locdict
|
971
1108
|
|
972
1109
|
def from_dict(self, props:dict):
|
973
1110
|
""" properties from dict """
|
974
1111
|
|
975
|
-
keys = ['color', 'linewidth', 'linestyle', 'marker', 'markersize', 'alpha',
|
1112
|
+
keys = ['color', 'linewidth', 'linestyle', 'marker', 'markersize', 'alpha',
|
1113
|
+
'label', 'markerfacecolor', 'markeredgecolor', 'markeredgewidth',
|
1114
|
+
'visible', 'zorder', 'picker', 'picker_radius',
|
1115
|
+
'xscale', 'yscale', 'xorigin_world', 'yorigin_world', 'xorigin_local', 'yorigin_local']
|
976
1116
|
|
977
1117
|
for key in keys:
|
978
1118
|
try:
|
@@ -981,11 +1121,63 @@ class Matplolib_line_properties():
|
|
981
1121
|
logging.warning('Key not found in properties dict')
|
982
1122
|
pass
|
983
1123
|
|
1124
|
+
# ATTENTION : The next 2 lines are done in the to_dict method of the axes
|
1125
|
+
# xydata = np.array([props['xdata'], props['ydata']]).T
|
1126
|
+
# self.set_xydata(xydata)
|
1127
|
+
|
984
1128
|
self.populate()
|
985
1129
|
self.set_properties()
|
986
1130
|
|
987
1131
|
return self
|
988
1132
|
|
1133
|
+
@property
|
1134
|
+
def xscale(self):
|
1135
|
+
return self._scales[0]
|
1136
|
+
|
1137
|
+
@xscale.setter
|
1138
|
+
def xscale(self, value):
|
1139
|
+
self._scales[0] = value
|
1140
|
+
|
1141
|
+
@property
|
1142
|
+
def yscale(self):
|
1143
|
+
return self._scales[1]
|
1144
|
+
|
1145
|
+
@yscale.setter
|
1146
|
+
def yscale(self, value):
|
1147
|
+
self._scales[1] = value
|
1148
|
+
|
1149
|
+
@property
|
1150
|
+
def xorigin_world(self):
|
1151
|
+
return self._origin_world[0]
|
1152
|
+
|
1153
|
+
@xorigin_world.setter
|
1154
|
+
def xorigin_world(self, value):
|
1155
|
+
self._origin_world[0] = value
|
1156
|
+
|
1157
|
+
@property
|
1158
|
+
def yorigin_world(self):
|
1159
|
+
return self._origin_world[1]
|
1160
|
+
|
1161
|
+
@yorigin_world.setter
|
1162
|
+
def yorigin_world(self, value):
|
1163
|
+
self._origin_world[1] = value
|
1164
|
+
|
1165
|
+
@property
|
1166
|
+
def xorigin_local(self):
|
1167
|
+
return self._origin_local[0]
|
1168
|
+
|
1169
|
+
@xorigin_local.setter
|
1170
|
+
def xorigin_local(self, value):
|
1171
|
+
self._origin_local[0] = value
|
1172
|
+
|
1173
|
+
@property
|
1174
|
+
def yorigin_local(self):
|
1175
|
+
return self._origin_local[1]
|
1176
|
+
|
1177
|
+
@yorigin_local.setter
|
1178
|
+
def yorigin_local(self, value):
|
1179
|
+
self._origin_local[1] = value
|
1180
|
+
|
989
1181
|
def add_props_to_sizer(self, frame:wx.Frame, sizer:wx.BoxSizer):
|
990
1182
|
""" Add the properties to a sizer """
|
991
1183
|
|
@@ -1013,8 +1205,9 @@ class Matplolib_line_properties():
|
|
1013
1205
|
self._line = None
|
1014
1206
|
|
1015
1207
|
class PRESET_LAYOUTS(Enum):
|
1016
|
-
DEFAULT = (1,1)
|
1017
|
-
MAT2X2 = (2,2)
|
1208
|
+
DEFAULT = (1,1, 'auto')
|
1209
|
+
MAT2X2 = (2,2, 'auto')
|
1210
|
+
DEFAULT_EQUAL = (1,1, 'equal')
|
1018
1211
|
class Matplotlib_Figure(wx.Frame):
|
1019
1212
|
""" Matplotlib Figure with wx Frame """
|
1020
1213
|
|
@@ -1043,7 +1236,8 @@ class Matplotlib_Figure(wx.Frame):
|
|
1043
1236
|
|
1044
1237
|
self.wx_exists = wx.App.Get() is not None
|
1045
1238
|
|
1046
|
-
self.fig =
|
1239
|
+
self.fig = Figure()
|
1240
|
+
# self.fig.set_visible(False)
|
1047
1241
|
dpi = self.fig.get_dpi()
|
1048
1242
|
size_x, size_y = self.fig.get_size_inches()
|
1049
1243
|
|
@@ -1053,10 +1247,22 @@ class Matplotlib_Figure(wx.Frame):
|
|
1053
1247
|
self.ax_dict:dict[str,Axes] = {} # dict of axes
|
1054
1248
|
self.ax:list[Axes] = [] # list of axes -- always flatten
|
1055
1249
|
self.shown_props = None # shown properties
|
1250
|
+
self._shiftdown = False
|
1251
|
+
|
1252
|
+
self._action = None
|
1253
|
+
self._keep_first_point = True
|
1056
1254
|
|
1057
1255
|
self.apply_layout(layout) # apply the layout
|
1058
1256
|
pass
|
1059
1257
|
|
1258
|
+
@property
|
1259
|
+
def action(self):
|
1260
|
+
return self._action
|
1261
|
+
|
1262
|
+
@action.setter
|
1263
|
+
def action(self, value):
|
1264
|
+
self._action = value
|
1265
|
+
|
1060
1266
|
def presets(self, which:PRESET_LAYOUTS = PRESET_LAYOUTS.DEFAULT):
|
1061
1267
|
""" Presets """
|
1062
1268
|
|
@@ -1102,12 +1308,12 @@ class Matplotlib_Figure(wx.Frame):
|
|
1102
1308
|
# store the axes in a list -- So we can access them by index, not only by name
|
1103
1309
|
self.ax = [ax for ax in self.ax_dict.values()]
|
1104
1310
|
else:
|
1105
|
-
# Tuple or list of
|
1106
|
-
if len(layout) !=
|
1107
|
-
logging.warning('Layout must be a tuple or a list of
|
1311
|
+
# Tuple or list of 3 elements - subplots
|
1312
|
+
if len(layout) != 3:
|
1313
|
+
logging.warning('Layout must be a tuple or a list of 3 elements (nbrows:int, nbcols:int, aspect_ratio:str|float)')
|
1108
1314
|
return
|
1109
1315
|
|
1110
|
-
self.nbrows, self.nbcols = layout
|
1316
|
+
self.nbrows, self.nbcols, ratio = layout
|
1111
1317
|
if self.nbrows*self.nbcols == 1:
|
1112
1318
|
# Convert to list -- subplots returns a single Axes but we want a list
|
1113
1319
|
self.ax = [self.fig.subplots(self.nbrows, self.nbcols)]
|
@@ -1115,6 +1321,14 @@ class Matplotlib_Figure(wx.Frame):
|
|
1115
1321
|
# Flatten the axes -- sbplots returns a 2D array of Axes but we want a list
|
1116
1322
|
self.ax = self.fig.subplots(self.nbrows, self.nbcols).flatten()
|
1117
1323
|
|
1324
|
+
for curax in self.ax:
|
1325
|
+
if ratio == 'auto':
|
1326
|
+
curax.set_aspect('auto')
|
1327
|
+
elif ratio == 'equal':
|
1328
|
+
curax.set_aspect('equal')
|
1329
|
+
else:
|
1330
|
+
curax.set_aspect(ratio)
|
1331
|
+
|
1118
1332
|
# store the axes in a dict -- So we can access them by name, not only by index
|
1119
1333
|
self.ax_dict = {f'{i}':ax for i, ax in enumerate(self.ax)}
|
1120
1334
|
for key,ax in self.ax_dict.items():
|
@@ -1192,6 +1406,7 @@ class Matplotlib_Figure(wx.Frame):
|
|
1192
1406
|
self._canvas.mpl_connect('motion_notify_event', self.UpdateStatusBar)
|
1193
1407
|
self._canvas.mpl_connect('button_press_event', self.OnClickCanvas)
|
1194
1408
|
self._canvas.mpl_connect('key_press_event', self.OnKeyCanvas)
|
1409
|
+
self._canvas.mpl_connect('key_release_event', self.OnKeyRelease)
|
1195
1410
|
|
1196
1411
|
# Toolbar - Matplotlib
|
1197
1412
|
# --------------------
|
@@ -1280,6 +1495,9 @@ class Matplotlib_Figure(wx.Frame):
|
|
1280
1495
|
self._add_row = wx.Button(win, -1, 'Add rows')
|
1281
1496
|
self._add_row.Bind(wx.EVT_BUTTON, self.add_row_to_grid)
|
1282
1497
|
|
1498
|
+
self._new_line = wx.Button(win, -1, 'New line')
|
1499
|
+
self._new_line.Bind(wx.EVT_BUTTON, self.onnew_line)
|
1500
|
+
|
1283
1501
|
self._add_line = wx.Button(win, -1, 'Add line')
|
1284
1502
|
self._add_line.Bind(wx.EVT_BUTTON, self.onadd_line)
|
1285
1503
|
|
@@ -1287,10 +1505,19 @@ class Matplotlib_Figure(wx.Frame):
|
|
1287
1505
|
self._del_line.Bind(wx.EVT_BUTTON, self.ondel_line)
|
1288
1506
|
|
1289
1507
|
self._sizer_xls.Add(self._xls, 1, wx.EXPAND)
|
1290
|
-
|
1291
|
-
self.
|
1292
|
-
self.
|
1293
|
-
|
1508
|
+
|
1509
|
+
self._sizer_update_add = wx.BoxSizer(wx.HORIZONTAL)
|
1510
|
+
self._sizer_add_remove = wx.BoxSizer(wx.HORIZONTAL)
|
1511
|
+
|
1512
|
+
self._sizer_xls.Add(self._sizer_update_add, 0, wx.EXPAND)
|
1513
|
+
self._sizer_xls.Add(self._sizer_add_remove, 0, wx.EXPAND)
|
1514
|
+
|
1515
|
+
self._sizer_update_add.Add(self._update_xy, 1, wx.EXPAND)
|
1516
|
+
self._sizer_update_add.Add(self._add_row, 1, wx.EXPAND)
|
1517
|
+
|
1518
|
+
self._sizer_add_remove.Add(self._new_line, 1, wx.EXPAND)
|
1519
|
+
self._sizer_add_remove.Add(self._add_line, 1, wx.EXPAND)
|
1520
|
+
self._sizer_add_remove.Add(self._del_line, 1, wx.EXPAND)
|
1294
1521
|
|
1295
1522
|
# Properties sizer
|
1296
1523
|
# ---------------
|
@@ -1396,7 +1623,11 @@ class Matplotlib_Figure(wx.Frame):
|
|
1396
1623
|
|
1397
1624
|
@property
|
1398
1625
|
def cur_line(self) -> Line2D:
|
1399
|
-
|
1626
|
+
|
1627
|
+
if self._line_current.GetSelection() == -1:
|
1628
|
+
return None
|
1629
|
+
else:
|
1630
|
+
return self.cur_ax.get_lines()[int(self._line_current.GetSelection())]
|
1400
1631
|
|
1401
1632
|
def get_figax(self):
|
1402
1633
|
|
@@ -1447,6 +1678,10 @@ class Matplotlib_Figure(wx.Frame):
|
|
1447
1678
|
def show_curline_properties(self):
|
1448
1679
|
# self.cur_line_properties.ui()
|
1449
1680
|
self._hide_all_props()
|
1681
|
+
|
1682
|
+
if self._line_current.GetSelection() == -1:
|
1683
|
+
return
|
1684
|
+
|
1450
1685
|
self.cur_line_properties.show_props()
|
1451
1686
|
# self.Layout()
|
1452
1687
|
self._sizer_grid_props.Layout()
|
@@ -1519,13 +1754,25 @@ class Matplotlib_Figure(wx.Frame):
|
|
1519
1754
|
|
1520
1755
|
if event.key == 'escape':
|
1521
1756
|
self._axes_properties[int(self._ax_current.GetSelection())].reset_selection()
|
1757
|
+
elif event.key == 'shift':
|
1758
|
+
self._shiftdown = True
|
1759
|
+
elif event.key == 'enter':
|
1760
|
+
if self.action is not None:
|
1761
|
+
action, callback = self.action
|
1762
|
+
if action == 'Digitize':
|
1763
|
+
callback((0,0), 'End Digitize')
|
1764
|
+
|
1765
|
+
def OnKeyRelease(self, event:KeyEvent):
|
1766
|
+
if event.key == 'shift':
|
1767
|
+
self._shiftdown = False
|
1522
1768
|
|
1523
1769
|
def OnClickCanvas(self, event:MouseEvent):
|
1524
1770
|
|
1525
1771
|
rclick = event.button == 3
|
1526
1772
|
lclick = event.button == 1
|
1773
|
+
middle = event.button == 2
|
1527
1774
|
|
1528
|
-
if not rclick:
|
1775
|
+
if not (rclick or middle):
|
1529
1776
|
return
|
1530
1777
|
|
1531
1778
|
if event.inaxes:
|
@@ -1533,21 +1780,81 @@ class Matplotlib_Figure(wx.Frame):
|
|
1533
1780
|
idx= ax.get_figure().axes.index(event.inaxes)
|
1534
1781
|
x, y = event.xdata, event.ydata
|
1535
1782
|
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1783
|
+
if middle:
|
1784
|
+
if self.action is not None:
|
1785
|
+
action, callback = self.action
|
1786
|
+
if action == 'Digitize':
|
1787
|
+
callback((0,0), 'End Digitize')
|
1788
|
+
|
1789
|
+
if rclick:
|
1790
|
+
|
1791
|
+
if self._shiftdown:
|
1792
|
+
# add a point to the current line, update the grid and plot
|
1793
|
+
xy = self.cur_line.get_xydata()
|
1794
|
+
|
1795
|
+
if xy.shape[0] == 1:
|
1796
|
+
if self._keep_first_point:
|
1797
|
+
xy = np.vstack((xy, [x,y]))
|
1798
|
+
else:
|
1799
|
+
xy = np.asarray([[x,y]])
|
1800
|
+
self._keep_first_point = True
|
1801
|
+
else:
|
1802
|
+
xy = np.vstack((xy, [x,y]))
|
1803
|
+
|
1804
|
+
self.cur_line.set_data(xy[:,0], xy[:,1])
|
1805
|
+
self.fill_grid_with_xy_np(self.cur_line_properties.get_xydata())
|
1806
|
+
self._canvas.draw()
|
1807
|
+
|
1808
|
+
if self.action is not None:
|
1809
|
+
|
1810
|
+
action, callback = self.action
|
1811
|
+
|
1812
|
+
if action == 'Digitize':
|
1813
|
+
if not self._shiftdown:
|
1814
|
+
logging.warning('Shift must be down to digitize')
|
1815
|
+
elif action == 'Ref X':
|
1816
|
+
if not self._shiftdown:
|
1817
|
+
logging.warning('Shift must be down to set X reference')
|
1818
|
+
else:
|
1819
|
+
callback((x,y), action)
|
1820
|
+
elif action == 'Ref Y':
|
1821
|
+
if not self._shiftdown:
|
1822
|
+
logging.warning('Shift must be down to set Y reference')
|
1823
|
+
else:
|
1824
|
+
callback((x,y), action)
|
1825
|
+
elif action == 'Origin':
|
1826
|
+
if not self._shiftdown:
|
1827
|
+
logging.warning('Shift must be down to set origin')
|
1828
|
+
else:
|
1829
|
+
callback((x,y), action)
|
1830
|
+
|
1831
|
+
elif not self._shiftdown:
|
1832
|
+
# Find the closest line and select it
|
1833
|
+
|
1834
|
+
lines = ax.get_lines()
|
1835
|
+
if len(lines) == 0:
|
1836
|
+
logging.warning('No lines !')
|
1837
|
+
return
|
1838
|
+
|
1839
|
+
dist_min = 1e6
|
1840
|
+
line_min = None
|
1841
|
+
|
1842
|
+
for line in lines:
|
1843
|
+
xy = line.get_xydata()
|
1844
|
+
if xy.shape[0] == 0:
|
1845
|
+
continue
|
1846
|
+
|
1847
|
+
dist = np.linalg.norm(xy - np.array([x,y]), axis=1)
|
1848
|
+
idx_min = np.argmin(dist)
|
1849
|
+
if dist[idx_min] < dist_min:
|
1850
|
+
dist_min = dist[idx_min]
|
1851
|
+
line_min = line
|
1852
|
+
|
1853
|
+
self._ax_current.SetSelection(idx)
|
1854
|
+
self._fill_lines_ax(idx = lines.index(line_min))
|
1855
|
+
self._axes_properties[idx].select_line(lines.index(line_min))
|
1856
|
+
|
1857
|
+
self.fill_grid_with_xy_np(self.cur_line_properties.get_xydata())
|
1551
1858
|
|
1552
1859
|
self.show_curline_properties()
|
1553
1860
|
|
@@ -1572,6 +1879,27 @@ class Matplotlib_Figure(wx.Frame):
|
|
1572
1879
|
grid.SetCellValue(i, colx, str(xy[i,0]))
|
1573
1880
|
grid.SetCellValue(i, coly, str(xy[i,1]))
|
1574
1881
|
|
1882
|
+
def fill_grid_with_xy_np(self, xy:np.ndarray, grid:CpGrid = None, colx:int= 0, coly:int= 1):
|
1883
|
+
|
1884
|
+
if grid is None:
|
1885
|
+
grid = self._xls
|
1886
|
+
|
1887
|
+
grid.ClearGrid()
|
1888
|
+
|
1889
|
+
if grid.GetNumberRows() < len(xy):
|
1890
|
+
grid.AppendRows(len(xy)-grid.GetNumberRows())
|
1891
|
+
elif grid.GetNumberRows() > len(xy):
|
1892
|
+
grid.DeleteRows(len(xy), grid.GetNumberRows()-len(xy))
|
1893
|
+
|
1894
|
+
for i in range(len(xy)):
|
1895
|
+
grid.SetCellValue(i, colx, str(xy[i,0]))
|
1896
|
+
grid.SetCellValue(i, coly, str(xy[i,1]))
|
1897
|
+
|
1898
|
+
def get_xy_from_grid(self):
|
1899
|
+
""" Get the xy from the grid """
|
1900
|
+
|
1901
|
+
return self._get_xy_from_grid(self._xls)
|
1902
|
+
|
1575
1903
|
def update_line_from_grid(self, event):
|
1576
1904
|
|
1577
1905
|
line = self.cur_line
|
@@ -1588,7 +1916,8 @@ class Matplotlib_Figure(wx.Frame):
|
|
1588
1916
|
xy[i,0] = float(self._xls.GetCellValue(i, 0))
|
1589
1917
|
xy[i,1] = float(self._xls.GetCellValue(i, 1))
|
1590
1918
|
|
1591
|
-
|
1919
|
+
self.cur_line_properties.set_xydata(xy)
|
1920
|
+
self._canvas.draw()
|
1592
1921
|
self.update_layout()
|
1593
1922
|
|
1594
1923
|
def add_row_to_grid(self, event):
|
@@ -1609,6 +1938,23 @@ class Matplotlib_Figure(wx.Frame):
|
|
1609
1938
|
xy = self._get_xy_from_grid(self._xls)
|
1610
1939
|
self.add_line(xy, self.cur_ax)
|
1611
1940
|
|
1941
|
+
def onnew_line(self, event):
|
1942
|
+
""" Add a plot to the current ax """
|
1943
|
+
self.new_line()
|
1944
|
+
|
1945
|
+
def new_line(self, ax:Axes=None, **kwargs) -> Matplolib_line_properties:
|
1946
|
+
""" Add a plot to the current ax """
|
1947
|
+
|
1948
|
+
curline = self.cur_line
|
1949
|
+
if curline is not None:
|
1950
|
+
xy = curline.get_xydata()
|
1951
|
+
xy = np.asarray([[xy[0,0],xy[0,1]]])
|
1952
|
+
else:
|
1953
|
+
xy = np.asarray([[0,0]])
|
1954
|
+
|
1955
|
+
self._keep_first_point = False
|
1956
|
+
return self.add_line(xy, ax, **kwargs)
|
1957
|
+
|
1612
1958
|
def _get_xy_from_grid(self, grid:CpGrid, colx:int= 0, coly:int= 1):
|
1613
1959
|
""" Get the xy from a grid """
|
1614
1960
|
|
@@ -1627,7 +1973,7 @@ class Matplotlib_Figure(wx.Frame):
|
|
1627
1973
|
|
1628
1974
|
return xy
|
1629
1975
|
|
1630
|
-
def add_line(self, xy:np.ndarray, ax:Axes=None, **kwargs):
|
1976
|
+
def add_line(self, xy:np.ndarray, ax:Axes=None, **kwargs) -> Matplolib_line_properties:
|
1631
1977
|
""" Add a plot to the current ax """
|
1632
1978
|
|
1633
1979
|
ax, idx_ax = self.get_ax_idx(ax)
|
@@ -1637,23 +1983,44 @@ class Matplotlib_Figure(wx.Frame):
|
|
1637
1983
|
cur_ax_prop:Matplotlib_ax_properties = self._axes_properties[idx_ax]
|
1638
1984
|
cur_ax_prop._lines.append(Matplolib_line_properties(ax.get_lines()[-1], cur_ax_prop))
|
1639
1985
|
cur_ax_prop._lines[-1].add_props_to_sizer(self._collaps_pane.GetPane(), self._sizer_grid_props)
|
1986
|
+
|
1987
|
+
self._fill_lines_ax(len(ax.get_lines())-1)
|
1988
|
+
|
1640
1989
|
self.update_layout()
|
1990
|
+
self._canvas.SetFocus()
|
1991
|
+
|
1992
|
+
return self.cur_ax_properties._lines[-1]
|
1993
|
+
|
1994
|
+
def add_image(self, image:np.ndarray | str, ax:Axes= None, **kwargs):
|
1995
|
+
|
1996
|
+
ax, idx_ax = self.get_ax_idx(ax)
|
1997
|
+
|
1998
|
+
if isinstance(image, str):
|
1999
|
+
image = ImageOps.flip(Image.open(image))
|
2000
|
+
ax.axis('off') # clear x-axis and y-axis
|
2001
|
+
|
2002
|
+
ax.imshow(image, **kwargs)
|
2003
|
+
|
2004
|
+
self.update_layout()
|
2005
|
+
self._canvas.SetFocus()
|
1641
2006
|
|
1642
2007
|
def ondel_line(self, event):
|
1643
2008
|
""" Remove a plot from the current ax """
|
1644
2009
|
|
2010
|
+
if self._line_current.GetSelection() == -1:
|
2011
|
+
return
|
2012
|
+
|
1645
2013
|
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
2014
|
|
1647
2015
|
ret = dlg.ShowModal()
|
1648
2016
|
if ret == wx.ID_NO:
|
1649
2017
|
return
|
1650
2018
|
|
1651
|
-
if self._line_current.GetSelection() == -1:
|
1652
|
-
return
|
1653
|
-
|
1654
2019
|
idx = self._line_current.GetSelection()
|
1655
2020
|
self.del_line(idx)
|
1656
2021
|
|
2022
|
+
self._fill_lines_ax()
|
2023
|
+
|
1657
2024
|
def del_line(self, idx:int):
|
1658
2025
|
""" Delete a line """
|
1659
2026
|
|