tikzplot42 0.2.8__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.
- tikzplot/__init__.py +5 -0
- tikzplot/__init__.pyi +5 -0
- tikzplot/axes.py +717 -0
- tikzplot/axes.pyi +445 -0
- tikzplot/axes3d.py +566 -0
- tikzplot/colorbar.py +261 -0
- tikzplot/colorbar.pyi +128 -0
- tikzplot/colors.py +58 -0
- tikzplot/colors.pyi +1 -0
- tikzplot/config.py +101 -0
- tikzplot/config.pyi +95 -0
- tikzplot/elements.py +641 -0
- tikzplot/elements.pyi +11 -0
- tikzplot/figure.py +285 -0
- tikzplot/figure.pyi +27 -0
- tikzplot/latex_special.py +62 -0
- tikzplot/plots.py +175 -0
- tikzplot/plots.pyi +314 -0
- tikzplot/py.typed +0 -0
- tikzplot/state.py +24 -0
- tikzplot/state.pyi +3 -0
- tikzplot42-0.2.8.dist-info/METADATA +780 -0
- tikzplot42-0.2.8.dist-info/RECORD +26 -0
- tikzplot42-0.2.8.dist-info/WHEEL +5 -0
- tikzplot42-0.2.8.dist-info/licenses/LICENSE +674 -0
- tikzplot42-0.2.8.dist-info/top_level.txt +1 -0
tikzplot/elements.py
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from .config import TikzConfig
|
|
5
|
+
from .state import next_export_num, main_name
|
|
6
|
+
from .colors import _tex_color
|
|
7
|
+
|
|
8
|
+
class BaseGraph:
|
|
9
|
+
_COLOR_MAP = {'b':'blue', 'g':'teal', 'r':'red', 'c':'cyan', 'm':'magenta', 'y':'yellow', 'k':'black', 'w':'white', "orange":"orange", "green": "green", "cyan":"cyan", "peru": "brown", "lime": "lime", "gray": "gray", "magenta": "magetna", "purple": "violet"}
|
|
10
|
+
_LINE_MAP = {"--": "dashed", ":": "dotted", "-.": "dashdotted", "-":"solid"}
|
|
11
|
+
_MARKER_MAP = {'o':'*', ".": "*", 's':'square*', '^':'triangle', 'v':'triangle*', 'd':'diamond', '+':'+', 'x':'x', '*':'star'}
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._axes = None
|
|
15
|
+
self._classic = False
|
|
16
|
+
self._style = None
|
|
17
|
+
self._label = None
|
|
18
|
+
self._settings = None
|
|
19
|
+
if self._settings == None: self._settings = []
|
|
20
|
+
|
|
21
|
+
self._opacity = 1
|
|
22
|
+
self._path_name = None
|
|
23
|
+
self._has_color = False
|
|
24
|
+
self._style_str = None
|
|
25
|
+
|
|
26
|
+
def _normalize_error(self, err, n):
|
|
27
|
+
if err is None:
|
|
28
|
+
return None, False
|
|
29
|
+
if isinstance(err, (int, float)):
|
|
30
|
+
return np.asarray([err] * n), False
|
|
31
|
+
if len(err) == n:
|
|
32
|
+
if hasattr(err[0], "__len__") and len(err[0]) == 2:
|
|
33
|
+
return np.asarray(err), True
|
|
34
|
+
return np.asarray(err), False
|
|
35
|
+
raise ValueError("Invalid errorbar specification")
|
|
36
|
+
|
|
37
|
+
def _style_string(self):
|
|
38
|
+
if self._style_str != None:
|
|
39
|
+
return self._style_str
|
|
40
|
+
opts = []
|
|
41
|
+
|
|
42
|
+
def match_ls(input):
|
|
43
|
+
if input in self._LINE_MAP.keys():
|
|
44
|
+
return self._LINE_MAP[input]
|
|
45
|
+
if input in self._LINE_MAP.values():
|
|
46
|
+
return input
|
|
47
|
+
print(f"Unrecognized linestyle {input}")
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def match_mark(input):
|
|
51
|
+
if input in self._MARKER_MAP.keys():
|
|
52
|
+
return self._MARKER_MAP[input]
|
|
53
|
+
if input in self._MARKER_MAP.values():
|
|
54
|
+
return input
|
|
55
|
+
print(f"Unrecognized marker {input}")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def match_color(input):
|
|
59
|
+
self._has_color = True
|
|
60
|
+
ccode, op = _tex_color(input)
|
|
61
|
+
if not isinstance(op, bool):
|
|
62
|
+
self._opacity = op
|
|
63
|
+
if isinstance(ccode, str):
|
|
64
|
+
return ccode
|
|
65
|
+
r,g,b=ccode
|
|
66
|
+
self._axes._add_col(r,g,b)
|
|
67
|
+
return f"c{r:.3f}{g:.3f}{b:.3f}".replace(".", "")
|
|
68
|
+
|
|
69
|
+
if "fmt" in self._style:
|
|
70
|
+
fmt = self._style["fmt"]
|
|
71
|
+
col = list(set(self._COLOR_MAP.keys()) & set(fmt))
|
|
72
|
+
if col:
|
|
73
|
+
opts.append(f"color={self._COLOR_MAP[col[0]]}")
|
|
74
|
+
fmt = fmt.replace(col[0], "")
|
|
75
|
+
self._has_color = True
|
|
76
|
+
mark = list(set(self._MARKER_MAP.keys()) & set(fmt))
|
|
77
|
+
if mark:
|
|
78
|
+
opts.append(f"mark={self._MARKER_MAP[mark[0]]}")
|
|
79
|
+
fmt = fmt.replace(mark[0], "")
|
|
80
|
+
ls = None
|
|
81
|
+
if fmt:
|
|
82
|
+
ls = match_ls(fmt)
|
|
83
|
+
if ls:
|
|
84
|
+
opts.append(ls)
|
|
85
|
+
|
|
86
|
+
if "c" in self._style:
|
|
87
|
+
sel_col = match_color(self._style['c'])
|
|
88
|
+
if sel_col:
|
|
89
|
+
opts.append(f"color={{{sel_col}}}")
|
|
90
|
+
if "color" in self._style:
|
|
91
|
+
sel_col = match_color(self._style['color'])
|
|
92
|
+
if sel_col:
|
|
93
|
+
opts.append(f"color={{{sel_col}}}")
|
|
94
|
+
if "ls" in self._style:
|
|
95
|
+
ls = self._style["ls"]
|
|
96
|
+
if ls == "":
|
|
97
|
+
opts.append("only marks")
|
|
98
|
+
else:
|
|
99
|
+
sel_ls = match_ls(ls)
|
|
100
|
+
if sel_ls:
|
|
101
|
+
opts.append(sel_ls)
|
|
102
|
+
if "linestyle" in self._style:
|
|
103
|
+
ls = self._style["linestyle"]
|
|
104
|
+
if ls == "":
|
|
105
|
+
opts.append("only marks")
|
|
106
|
+
else:
|
|
107
|
+
sel_ls = match_ls(ls)
|
|
108
|
+
if sel_ls:
|
|
109
|
+
opts.append(sel_ls)
|
|
110
|
+
if "lw" in self._style:
|
|
111
|
+
opts.append(f"line width={self._style['lw']}pt")
|
|
112
|
+
if "linewidth" in self._style:
|
|
113
|
+
opts.append(f"line width={self._style['linewidth']}pt")
|
|
114
|
+
if "marker" in self._style:
|
|
115
|
+
sel_mark = match_mark(self._style['marker'])
|
|
116
|
+
if sel_mark:
|
|
117
|
+
opts.append(f"mark={sel_mark}")
|
|
118
|
+
if "ms" in self._style:
|
|
119
|
+
opts.append(f"mark size={self._style['ms']}pt")
|
|
120
|
+
if "marksize" in self._style:
|
|
121
|
+
opts.append(f"mark size={self._style['marksize']}pt")
|
|
122
|
+
if "markerfmt" in self._style:
|
|
123
|
+
col = list(set(self._COLOR_MAP.keys()) & set(fmt))
|
|
124
|
+
if col:
|
|
125
|
+
opts.append(f"mark options={{{self._COLOR_MAP[col[0]]}}}")
|
|
126
|
+
fmt = fmt.replace(col[0], "")
|
|
127
|
+
self._has_color = True
|
|
128
|
+
mark = list(set(self._MARKER_MAP.keys()) & set(fmt))
|
|
129
|
+
if mark:
|
|
130
|
+
opts.append(f"mark={self._MARKER_MAP[mark[0]]}")
|
|
131
|
+
fmt = fmt.replace(mark[0], "")
|
|
132
|
+
if "label" in self._style:
|
|
133
|
+
self._label = self._style["label"]
|
|
134
|
+
|
|
135
|
+
if "alpha" in self._style:
|
|
136
|
+
self._opacity = self._style["alpha"]
|
|
137
|
+
|
|
138
|
+
#if "onlayer" in self._style:
|
|
139
|
+
# opts.append(f"on layer={self._style["onlayer"]}")
|
|
140
|
+
|
|
141
|
+
if self._opacity < 1:
|
|
142
|
+
opts.append(f"opacity={self._opacity}")
|
|
143
|
+
if not self._has_color and self._classic:
|
|
144
|
+
opts.append(f"color={{{match_color(f'C{self._axes._get_defcol()}')}}}")
|
|
145
|
+
if self._classic:
|
|
146
|
+
if self._xerr is not None or self._yerr is not None or (isinstance(self, Graph3) and self._zerr is not None):
|
|
147
|
+
opts.append("error bars/.cd")
|
|
148
|
+
if self._xerr is not None:
|
|
149
|
+
opts.append("x dir=both")
|
|
150
|
+
opts.append("x explicit")
|
|
151
|
+
if self._yerr is not None:
|
|
152
|
+
opts.append("y dir=both")
|
|
153
|
+
opts.append("y explicit")
|
|
154
|
+
if isinstance(self, Graph3) and self._zerr is not None:
|
|
155
|
+
opts.append("z dir=both")
|
|
156
|
+
opts.append("z explicit")
|
|
157
|
+
keys = {}
|
|
158
|
+
for i in reversed(range(len(opts))):
|
|
159
|
+
key = str(opts[i]).split("=")
|
|
160
|
+
if key[0] in keys:
|
|
161
|
+
del opts[i]
|
|
162
|
+
if self._path_name: self._settings.append(f"name path={self._path_name}")
|
|
163
|
+
if self._settings:
|
|
164
|
+
opts = self._settings + opts
|
|
165
|
+
self._style_str = ",\n".join(str(o) for o in opts)
|
|
166
|
+
return self._style_str
|
|
167
|
+
|
|
168
|
+
def _save_data(self, points, filename):
|
|
169
|
+
path = Path(filename)
|
|
170
|
+
file_number = next_export_num()
|
|
171
|
+
current = path.parent
|
|
172
|
+
dir = current / TikzConfig.DATAPOINTS_DIR
|
|
173
|
+
dir.mkdir(parents=True, exist_ok=True)
|
|
174
|
+
file_name = dir / f"{str(path.stem)}_{file_number}.dat"
|
|
175
|
+
if not TikzConfig.UPDATE_STYLE_ONLY:
|
|
176
|
+
with file_name.open("w", encoding="utf-8") as f:
|
|
177
|
+
f.write(points)
|
|
178
|
+
return str(Path(TikzConfig.DATAPOINTS_DIR) / f"{str(path.stem)}_{file_number}.dat")
|
|
179
|
+
|
|
180
|
+
def _try_set_pname(self, pname):
|
|
181
|
+
if self._path_name:
|
|
182
|
+
return self._path_name
|
|
183
|
+
self._path_name = pname
|
|
184
|
+
return pname
|
|
185
|
+
|
|
186
|
+
def _num_points(self):
|
|
187
|
+
if self._classic:
|
|
188
|
+
return len(self._x)
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
def _set_label(self, lab):
|
|
192
|
+
self._style["label"] = lab
|
|
193
|
+
|
|
194
|
+
class Graph(BaseGraph):
|
|
195
|
+
def __init__(self, axes, coordinates, settings=None, xerr=None, yerr=None, path_name=None, **style):
|
|
196
|
+
super().__init__()
|
|
197
|
+
self._axes = axes
|
|
198
|
+
self._classic = False
|
|
199
|
+
if isinstance(coordinates, tuple):
|
|
200
|
+
self._classic = True
|
|
201
|
+
x,y=coordinates
|
|
202
|
+
self._x = np.asarray(x)
|
|
203
|
+
self._y = np.asarray(y)
|
|
204
|
+
mask = np.isfinite(self._x) & np.isfinite(self._y)
|
|
205
|
+
self._x = self._x[mask]
|
|
206
|
+
self._y = self._y[mask]
|
|
207
|
+
n = len(self._x)
|
|
208
|
+
self._xerr, self._x_asym = self._normalize_error(xerr, n)
|
|
209
|
+
self._yerr, self._y_asym = self._normalize_error(yerr, n)
|
|
210
|
+
if self._xerr is not None:
|
|
211
|
+
self._xerr = np.asarray(self._xerr)[mask]
|
|
212
|
+
|
|
213
|
+
if self._yerr is not None:
|
|
214
|
+
self._yerr = np.asarray(self._yerr)[mask]
|
|
215
|
+
else:
|
|
216
|
+
self._special = coordinates
|
|
217
|
+
self._style = style
|
|
218
|
+
self._label = None
|
|
219
|
+
self._settings = settings
|
|
220
|
+
if self._settings == None: self._settings = []
|
|
221
|
+
|
|
222
|
+
self._opacity = 1
|
|
223
|
+
self._path_name = path_name
|
|
224
|
+
self._has_color = False
|
|
225
|
+
self._style_str = None
|
|
226
|
+
|
|
227
|
+
def _header(self):
|
|
228
|
+
cols = ["x", "y"]
|
|
229
|
+
if self._xerr is not None:
|
|
230
|
+
if self._x_asym:
|
|
231
|
+
cols += ["xerrminus", "xerrplus"]
|
|
232
|
+
else:
|
|
233
|
+
cols.append("xerror")
|
|
234
|
+
if self._yerr is not None:
|
|
235
|
+
if self._y_asym:
|
|
236
|
+
cols += ["yerrminus", "yerrplus"]
|
|
237
|
+
else:
|
|
238
|
+
cols.append("yerror")
|
|
239
|
+
return " ".join(cols)
|
|
240
|
+
|
|
241
|
+
def _rows(self):
|
|
242
|
+
rows = []
|
|
243
|
+
for i in range(len(self._x)):
|
|
244
|
+
line = [self._x[i], self._y[i]]
|
|
245
|
+
if self._xerr is not None:
|
|
246
|
+
if self._x_asym:
|
|
247
|
+
line += list(self._xerr[i])
|
|
248
|
+
else:
|
|
249
|
+
line.append(self._xerr[i])
|
|
250
|
+
if self._yerr is not None:
|
|
251
|
+
if self._y_asym:
|
|
252
|
+
line += list(self._yerr[i])
|
|
253
|
+
else:
|
|
254
|
+
line.append(self._yerr[i])
|
|
255
|
+
rows.append(" ".join(str(v) for v in line))
|
|
256
|
+
return "\n".join(rows)
|
|
257
|
+
|
|
258
|
+
def _to_tex(self, filename):
|
|
259
|
+
style = self._style_string()
|
|
260
|
+
|
|
261
|
+
if self._classic:
|
|
262
|
+
header = self._header()
|
|
263
|
+
rows = self._rows()
|
|
264
|
+
table_opts = "x=x,y=y"
|
|
265
|
+
if self._xerr is not None:
|
|
266
|
+
table_opts += ",x error=xerror"
|
|
267
|
+
if self._yerr is not None:
|
|
268
|
+
table_opts += ",y error=yerror"
|
|
269
|
+
datapoints = f"{header}\n{rows}\n"
|
|
270
|
+
if TikzConfig.SAVE_DATAPOINTS:
|
|
271
|
+
datapoints = self._save_data(datapoints, filename)
|
|
272
|
+
if not TikzConfig.SAVE_DATAPOINTS or (TikzConfig.SAVE_DATAPOINTS and not TikzConfig.UPDATE_DATA_ONLY):
|
|
273
|
+
if self._label and self._axes._legend_on:
|
|
274
|
+
return f"\\addplot [{style}] table [{table_opts}] {{{datapoints}}};\\addlegendentry{{{self._label}}}"
|
|
275
|
+
return f"\\addplot [forget plot,\n{style}] table [{table_opts}] {{{datapoints}}};"
|
|
276
|
+
return ""
|
|
277
|
+
elif TikzConfig.SAVE_DATAPOINTS or not (TikzConfig.SAVE_DATAPOINTS and not TikzConfig.UPDATE_STYLE_ONLY):
|
|
278
|
+
return f"\\addplot [forget plot,\n{style}] {self._special};"
|
|
279
|
+
else:
|
|
280
|
+
return ""
|
|
281
|
+
|
|
282
|
+
def _data_range(self):
|
|
283
|
+
xmin, xmax = min(self._x), max(self._x)
|
|
284
|
+
ymin, ymax = min(self._y), max(self._y)
|
|
285
|
+
return xmin, xmax, ymin, ymax
|
|
286
|
+
|
|
287
|
+
def _get_erange(self, which):
|
|
288
|
+
if which == "xmin":
|
|
289
|
+
return min(self._x)
|
|
290
|
+
if which == "xmax":
|
|
291
|
+
return max(self._x)
|
|
292
|
+
if which == "ymin":
|
|
293
|
+
return min(self._y)
|
|
294
|
+
if which == "ymax":
|
|
295
|
+
return max(self._y)
|
|
296
|
+
|
|
297
|
+
def _filter(self, which, value):
|
|
298
|
+
if which == "xmin":
|
|
299
|
+
mask = self._x >= value
|
|
300
|
+
idx_keep = np.where(self._x < value)[0]
|
|
301
|
+
if len(idx_keep) > 0:
|
|
302
|
+
idx_keep = idx_keep[-1]
|
|
303
|
+
elif which == "xmax":
|
|
304
|
+
mask = self._x <= value
|
|
305
|
+
idx_keep = np.where(self._x > value)[0]
|
|
306
|
+
if len(idx_keep) > 0:
|
|
307
|
+
idx_keep = idx_keep[0]
|
|
308
|
+
elif which == "ymin":
|
|
309
|
+
mask = self._y >= value
|
|
310
|
+
idx_keep = np.where(self._y < value)[0]
|
|
311
|
+
if len(idx_keep) > 0:
|
|
312
|
+
idx_keep = idx_keep[-1]
|
|
313
|
+
elif which == "ymax":
|
|
314
|
+
mask = self._y <= value
|
|
315
|
+
idx_keep = np.where(self._y > value)[0]
|
|
316
|
+
if len(idx_keep) > 0:
|
|
317
|
+
idx_keep = idx_keep[0]
|
|
318
|
+
|
|
319
|
+
else:
|
|
320
|
+
raise ValueError("Invalid filter type")
|
|
321
|
+
|
|
322
|
+
mask[idx_keep] = True
|
|
323
|
+
|
|
324
|
+
self._x = self._x[mask]
|
|
325
|
+
self._y = self._y[mask]
|
|
326
|
+
|
|
327
|
+
if self._xerr is not None:
|
|
328
|
+
self._xerr = self._xerr[mask]
|
|
329
|
+
|
|
330
|
+
if self._yerr is not None:
|
|
331
|
+
self._yerr = self._yerr[mask]
|
|
332
|
+
|
|
333
|
+
def _check_equal(self, x,y):
|
|
334
|
+
if self._classic:
|
|
335
|
+
return np.array_equal(np.asarray(x),self._x) and np.array_equal(np.asarray(y),self._y)
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
def _reduce_points(self, limit):
|
|
339
|
+
if self._classic:
|
|
340
|
+
xm, xmode, xbase = self._axes._get_limit("xmin")
|
|
341
|
+
xM, _, _ = self._axes._get_limit("xmax")
|
|
342
|
+
ym, ymode, ybase = self._axes._get_limit("ymin")
|
|
343
|
+
yM, _, _ = self._axes._get_limit("ymax")
|
|
344
|
+
if xm == None: xm = self._get_erange("xmin")
|
|
345
|
+
if xM == None: xM = self._get_erange("xmax")
|
|
346
|
+
if ym == None: ym = self._get_erange("ymin")
|
|
347
|
+
if yM == None: yM = self._get_erange("ymax")
|
|
348
|
+
l = len(self._x)
|
|
349
|
+
if l > limit:
|
|
350
|
+
if TikzConfig.REDUCE_METHOD == 0:
|
|
351
|
+
idx_keep = np.linspace(0, l-1, limit, dtype=int)
|
|
352
|
+
self._x = self._x[idx_keep]
|
|
353
|
+
self._y = self._y[idx_keep]
|
|
354
|
+
if self._xerr is not None:
|
|
355
|
+
self._xerr = self._xerr[idx_keep]
|
|
356
|
+
if self._yerr is not None:
|
|
357
|
+
self._yerr = self._yerr[idx_keep]
|
|
358
|
+
elif TikzConfig.REDUCE_METHOD in [1,2]:
|
|
359
|
+
if xmode == "log":
|
|
360
|
+
fac = xM / xm
|
|
361
|
+
if fac > 0: fac = np.log(fac) / np.log(xbase)
|
|
362
|
+
else: fac = 1
|
|
363
|
+
vis_x = np.log(self._x) / fac
|
|
364
|
+
else:
|
|
365
|
+
fac = xM - xm
|
|
366
|
+
if fac == 0: fac = 1
|
|
367
|
+
vis_x = self._x / fac
|
|
368
|
+
if ymode == "log":
|
|
369
|
+
fac = yM / ym
|
|
370
|
+
if fac > 0: fac = np.log(fac) / np.log(ybase)
|
|
371
|
+
else: fac = 1
|
|
372
|
+
vis_y = np.log(self._y) / fac
|
|
373
|
+
else:
|
|
374
|
+
fac = yM - ym
|
|
375
|
+
if fac == 0: fac = 1
|
|
376
|
+
vis_y = self._y / fac
|
|
377
|
+
while len(self._x) > limit:
|
|
378
|
+
if TikzConfig.REDUCE_METHOD == 1:
|
|
379
|
+
dx1 = vis_x[1:-1] - vis_x[:-2]
|
|
380
|
+
dy1 = vis_y[1:-1] - vis_y[:-2]
|
|
381
|
+
dx2 = vis_x[2:] - vis_x[1:-1]
|
|
382
|
+
dy2 = vis_y[2:] - vis_y[1:-1]
|
|
383
|
+
crit = np.hypot(dx1, dy1) + np.hypot(dx2, dy2)
|
|
384
|
+
elif TikzConfig.REDUCE_METHOD == 2:
|
|
385
|
+
x0, x1, x2 = vis_x[:-2], vis_x[1:-1], vis_x[2:]
|
|
386
|
+
y0, y1, y2 = vis_y[:-2], vis_y[1:-1], vis_y[2:]
|
|
387
|
+
crit = np.abs((x1 - x0)*(y2 - y0) - (y1 - y0)*(x2 - x0))
|
|
388
|
+
idx_remove = np.argmin(crit)+1
|
|
389
|
+
if idx_remove == len(crit): idx_remove -= 1
|
|
390
|
+
mask = np.ones(len(self._x), dtype=bool)
|
|
391
|
+
mask[idx_remove] = False
|
|
392
|
+
self._x = self._x[mask]
|
|
393
|
+
self._y = self._y[mask]
|
|
394
|
+
vis_x = vis_x[mask]
|
|
395
|
+
vis_y = vis_y[mask]
|
|
396
|
+
if self._xerr is not None:
|
|
397
|
+
self._xerr = self._xerr[mask]
|
|
398
|
+
if self._yerr is not None:
|
|
399
|
+
self._yerr = self._yerr[mask]
|
|
400
|
+
|
|
401
|
+
class Graph3(BaseGraph):
|
|
402
|
+
def __init__(self, axes, coordinates, settings=None, xerr=None, yerr=None, zerr=None, path_name=None, **style):
|
|
403
|
+
super().__init__()
|
|
404
|
+
self._axes = axes
|
|
405
|
+
self._classic = False
|
|
406
|
+
if isinstance(coordinates, tuple):
|
|
407
|
+
self._classic = True
|
|
408
|
+
x,y,z=coordinates
|
|
409
|
+
self._x = np.asarray(x)
|
|
410
|
+
self._y = np.asarray(y)
|
|
411
|
+
self._z = np.asarray(z)
|
|
412
|
+
mask = np.isfinite(self._x) & np.isfinite(self._y) & np.isfinite(self._z)
|
|
413
|
+
self._x = self._x[mask]
|
|
414
|
+
self._y = self._y[mask]
|
|
415
|
+
self._z = self._z[mask]
|
|
416
|
+
n = len(self._x)
|
|
417
|
+
self._xerr, self._x_asym = self._normalize_error(xerr, n)
|
|
418
|
+
self._yerr, self._y_asym = self._normalize_error(yerr, n)
|
|
419
|
+
self._zerr, self._z_asym = self._normalize_error(zerr, n)
|
|
420
|
+
if self._xerr is not None:
|
|
421
|
+
self._xerr = np.asarray(self._xerr)[mask]
|
|
422
|
+
if self._yerr is not None:
|
|
423
|
+
self._yerr = np.asarray(self._yerr)[mask]
|
|
424
|
+
if self._zerr is not None:
|
|
425
|
+
self._zerr = np.asarray(self._zerr)[mask]
|
|
426
|
+
else:
|
|
427
|
+
self._special = coordinates
|
|
428
|
+
self._style = style
|
|
429
|
+
self._label = None
|
|
430
|
+
self._settings = settings
|
|
431
|
+
if self._settings == None: self._settings = []
|
|
432
|
+
|
|
433
|
+
self._opacity = 1
|
|
434
|
+
self._path_name = path_name
|
|
435
|
+
self._has_color = False
|
|
436
|
+
self._style_str = None
|
|
437
|
+
|
|
438
|
+
def _header(self):
|
|
439
|
+
cols = ["x", "y", "z"]
|
|
440
|
+
if self._xerr is not None:
|
|
441
|
+
if self._x_asym:
|
|
442
|
+
cols += ["xerrminus", "xerrplus"]
|
|
443
|
+
else:
|
|
444
|
+
cols.append("xerror")
|
|
445
|
+
if self._yerr is not None:
|
|
446
|
+
if self._y_asym:
|
|
447
|
+
cols += ["yerrminus", "yerrplus"]
|
|
448
|
+
else:
|
|
449
|
+
cols.append("yerror")
|
|
450
|
+
if self._zerr is not None:
|
|
451
|
+
if self._z_asym:
|
|
452
|
+
cols += ["zerrminus", "zerrplus"]
|
|
453
|
+
else:
|
|
454
|
+
cols.append("zerror")
|
|
455
|
+
return " ".join(cols)
|
|
456
|
+
|
|
457
|
+
def _rows(self):
|
|
458
|
+
rows = []
|
|
459
|
+
for i in range(len(self._x)):
|
|
460
|
+
line = [self._x[i], self._y[i], self._z[i]]
|
|
461
|
+
if self._xerr is not None:
|
|
462
|
+
if self._x_asym:
|
|
463
|
+
line += list(self._xerr[i])
|
|
464
|
+
else:
|
|
465
|
+
line.append(self._xerr[i])
|
|
466
|
+
if self._yerr is not None:
|
|
467
|
+
if self._y_asym:
|
|
468
|
+
line += list(self._yerr[i])
|
|
469
|
+
else:
|
|
470
|
+
line.append(self._yerr[i])
|
|
471
|
+
if self._zerr is not None:
|
|
472
|
+
if self._z_asym:
|
|
473
|
+
line += list(self._zerr[i])
|
|
474
|
+
else:
|
|
475
|
+
line.append(self._zerr[i])
|
|
476
|
+
rows.append(" ".join(str(v) for v in line))
|
|
477
|
+
return "\n".join(rows)
|
|
478
|
+
|
|
479
|
+
def _to_tex(self, filename):
|
|
480
|
+
style = self._style_string()
|
|
481
|
+
|
|
482
|
+
if self._classic:
|
|
483
|
+
header = self._header()
|
|
484
|
+
rows = self._rows()
|
|
485
|
+
table_opts = "x=x,y=y,z=z"
|
|
486
|
+
if self._xerr is not None:
|
|
487
|
+
table_opts += ",x error=xerror"
|
|
488
|
+
if self._yerr is not None:
|
|
489
|
+
table_opts += ",y error=yerror"
|
|
490
|
+
if self._zerr is not None:
|
|
491
|
+
table_opts += ",z error=zerror"
|
|
492
|
+
datapoints = f"{header}\n{rows}\n"
|
|
493
|
+
if TikzConfig.SAVE_DATAPOINTS:
|
|
494
|
+
datapoints = self._save_data(datapoints, filename).replace(r"\\", r"/")
|
|
495
|
+
if not TikzConfig.SAVE_DATAPOINTS or (TikzConfig.SAVE_DATAPOINTS and not TikzConfig.UPDATE_DATA_ONLY):
|
|
496
|
+
if self._label and self._axes._legend_on:
|
|
497
|
+
return f"\\addplot3 [{style}] table [{table_opts}] {{{datapoints}}};\\addlegendentry{{{self._label}}}"
|
|
498
|
+
return f"\\addplot3 [forget plot,\n{style}] table [{table_opts}] {{{datapoints}}};"
|
|
499
|
+
return ""
|
|
500
|
+
elif TikzConfig.SAVE_DATAPOINTS or not (TikzConfig.SAVE_DATAPOINTS and not TikzConfig.UPDATE_STYLE_ONLY):
|
|
501
|
+
return f"""\\addplot3 [forget plot,\n{style}] {self._special};"""
|
|
502
|
+
else:
|
|
503
|
+
return ""
|
|
504
|
+
|
|
505
|
+
def _data_range(self):
|
|
506
|
+
xmin, xmax = min(self._x), max(self._x)
|
|
507
|
+
ymin, ymax = min(self._y), max(self._y)
|
|
508
|
+
zmin, zmax = min(self._z), max(self._z)
|
|
509
|
+
return xmin, xmax, ymin, ymax, zmin, zmax
|
|
510
|
+
|
|
511
|
+
def _get_erange(self, which):
|
|
512
|
+
if which == "xmin":
|
|
513
|
+
return min(self._x)
|
|
514
|
+
if which == "xmax":
|
|
515
|
+
return max(self._x)
|
|
516
|
+
if which == "ymin":
|
|
517
|
+
return min(self._y)
|
|
518
|
+
if which == "ymax":
|
|
519
|
+
return max(self._y)
|
|
520
|
+
if which == "zmin":
|
|
521
|
+
return min(self._z)
|
|
522
|
+
if which == "zmax":
|
|
523
|
+
return max(self._z)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _filter(self, which, value):
|
|
527
|
+
if which == "xmin":
|
|
528
|
+
mask = self._x >= value
|
|
529
|
+
idx_keep = np.where(self._x < value)[0]
|
|
530
|
+
if len(idx_keep) > 0:
|
|
531
|
+
idx_keep = idx_keep[-1]
|
|
532
|
+
elif which == "xmax":
|
|
533
|
+
mask = self._x <= value
|
|
534
|
+
idx_keep = np.where(self._x > value)[0]
|
|
535
|
+
if len(idx_keep) > 0:
|
|
536
|
+
idx_keep = idx_keep[0]
|
|
537
|
+
elif which == "ymin":
|
|
538
|
+
mask = self._y >= value
|
|
539
|
+
idx_keep = np.where(self._y < value)[0]
|
|
540
|
+
if len(idx_keep) > 0:
|
|
541
|
+
idx_keep = idx_keep[-1]
|
|
542
|
+
elif which == "ymax":
|
|
543
|
+
mask = self._y <= value
|
|
544
|
+
idx_keep = np.where(self._y > value)[0]
|
|
545
|
+
if len(idx_keep) > 0:
|
|
546
|
+
idx_keep = idx_keep[0]
|
|
547
|
+
elif which == "zmin":
|
|
548
|
+
mask = self._z >= value
|
|
549
|
+
idx_keep = np.where(self._z < value)[0]
|
|
550
|
+
if len(idx_keep) > 0:
|
|
551
|
+
idx_keep = idx_keep[-1]
|
|
552
|
+
elif which == "zmax":
|
|
553
|
+
mask = self._z <= value
|
|
554
|
+
idx_keep = np.where(self._z > value)[0]
|
|
555
|
+
if len(idx_keep) > 0:
|
|
556
|
+
idx_keep = idx_keep[0]
|
|
557
|
+
|
|
558
|
+
else:
|
|
559
|
+
raise ValueError("Invalid filter type")
|
|
560
|
+
|
|
561
|
+
mask[idx_keep] = True
|
|
562
|
+
|
|
563
|
+
self._x = self._x[mask]
|
|
564
|
+
self._y = self._y[mask]
|
|
565
|
+
self._z = self._z[mask]
|
|
566
|
+
|
|
567
|
+
if self._xerr is not None:
|
|
568
|
+
self._xerr = self._xerr[mask]
|
|
569
|
+
|
|
570
|
+
if self._yerr is not None:
|
|
571
|
+
self._yerr = self._yerr[mask]
|
|
572
|
+
|
|
573
|
+
if self._zerr is not None:
|
|
574
|
+
self._zerr = self._zerr[mask]
|
|
575
|
+
|
|
576
|
+
def _check_equal(self, x,y,z):
|
|
577
|
+
if self._classic:
|
|
578
|
+
return np.array_equal(np.asarray(x),self._x) and np.array_equal(np.asarray(y),self._y) and np.array_equal(np.asarray(z),self._z)
|
|
579
|
+
return False
|
|
580
|
+
|
|
581
|
+
def _reduce_points(self, limit, logx=False, logy=False, logz=False):
|
|
582
|
+
if self._classic:
|
|
583
|
+
l = len(self._x)
|
|
584
|
+
if l > limit:
|
|
585
|
+
if TikzConfig.REDUCE_METHOD == 0:
|
|
586
|
+
idx_keep = np.linspace(0, l-1, limit, dtype=int)
|
|
587
|
+
self._x = self._x[idx_keep]
|
|
588
|
+
self._y = self._y[idx_keep]
|
|
589
|
+
self._z = self._z[idx_keep]
|
|
590
|
+
if self._xerr is not None:
|
|
591
|
+
self._xerr = self._xerr[idx_keep]
|
|
592
|
+
if self._yerr is not None:
|
|
593
|
+
self._yerr = self._yerr[idx_keep]
|
|
594
|
+
if self._zerr is not None:
|
|
595
|
+
self._zerr = self._zerr[idx_keep]
|
|
596
|
+
elif TikzConfig.REDUCE_METHOD in [1,2]:
|
|
597
|
+
if logx:
|
|
598
|
+
vis_x = np.log(self._x)
|
|
599
|
+
else:
|
|
600
|
+
vis_x = self._x
|
|
601
|
+
if logy:
|
|
602
|
+
vis_y = np.log(self._y)
|
|
603
|
+
else:
|
|
604
|
+
vis_y = self._y
|
|
605
|
+
if logz:
|
|
606
|
+
vis_z = np.log(self._z)
|
|
607
|
+
else:
|
|
608
|
+
vis_z = self._z
|
|
609
|
+
while len(self._x) > limit:
|
|
610
|
+
if TikzConfig.REDUCE_METHOD == 1:
|
|
611
|
+
dx1 = vis_x[1:-1] - vis_x[:-2]
|
|
612
|
+
dy1 = vis_y[1:-1] - vis_y[:-2]
|
|
613
|
+
dz1 = vis_z[1:-1] - vis_z[:-2]
|
|
614
|
+
dx2 = vis_x[2:] - vis_x[1:-1]
|
|
615
|
+
dy2 = vis_y[2:] - vis_y[1:-1]
|
|
616
|
+
dz2 = vis_z[2:] - vis_z[1:-1]
|
|
617
|
+
crit = np.hypot(np.hypot(dx1, dy1), dz1) + np.hypot(np.hypot(dx2, dy2), dz2)
|
|
618
|
+
elif TikzConfig.REDUCE_METHOD == 2:
|
|
619
|
+
x0, x1, x2 = vis_x[:-2], vis_x[1:-1], vis_x[2:]
|
|
620
|
+
y0, y1, y2 = vis_y[:-2], vis_y[1:-1], vis_y[2:]
|
|
621
|
+
z0, z1, z2 = vis_z[:-2], vis_z[1:-1], vis_z[2:]
|
|
622
|
+
v1 = np.stack([x1 - x0, y1 - y0, z1 - z0], axis=-1)
|
|
623
|
+
v2 = np.stack([x2 - x1, y2 - y1, z2 - z1], axis=-1)
|
|
624
|
+
cross = np.cross(v1, v2)
|
|
625
|
+
crit = np.linalg.norm(cross, axis=-1)
|
|
626
|
+
idx_remove = np.argmin(crit)+1
|
|
627
|
+
if idx_remove == len(crit): idx_remove -= 1
|
|
628
|
+
mask = np.ones(len(self._x), dtype=bool)
|
|
629
|
+
mask[idx_remove] = False
|
|
630
|
+
self._x = self._x[mask]
|
|
631
|
+
self._y = self._y[mask]
|
|
632
|
+
self._z = self._z[mask]
|
|
633
|
+
vis_x = vis_x[mask]
|
|
634
|
+
vis_y = vis_y[mask]
|
|
635
|
+
vis_z = vis_z[mask]
|
|
636
|
+
if self._xerr is not None:
|
|
637
|
+
self._xerr = self._xerr[mask]
|
|
638
|
+
if self._yerr is not None:
|
|
639
|
+
self._yerr = self._yerr[mask]
|
|
640
|
+
if self._zerr is not None:
|
|
641
|
+
self._zerr = self._zerr[mask]
|
tikzplot/elements.pyi
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .config import TikzConfig as TikzConfig
|
|
2
|
+
from .state import main_name as main_name, next_export_num as next_export_num
|
|
3
|
+
|
|
4
|
+
class Graph:
|
|
5
|
+
pass
|
|
6
|
+
""" def __init__(self, axes, coordinates, settings=None, xerr=None, yerr=None, path_name=None, **style) -> None: ...
|
|
7
|
+
def save_data(self, points, filename): ...
|
|
8
|
+
def to_tex(self, filename): ...
|
|
9
|
+
def data_range(self, which): ...
|
|
10
|
+
def get_erange(self, which): ...
|
|
11
|
+
def filter(self, which, value) -> None: ..."""
|