tikzplot42 0.4.3__tar.gz → 0.4.5__tar.gz

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.
Files changed (48) hide show
  1. {tikzplot42-0.4.3/src/tikzplot42.egg-info → tikzplot42-0.4.5}/PKG-INFO +1 -1
  2. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/pyproject.toml +1 -1
  3. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/axes.py +229 -50
  4. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/axes.pyi +28 -10
  5. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/axes3d.py +4 -4
  6. tikzplot42-0.4.5/src/tikzplot/colors.py +228 -0
  7. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/elements.py +59 -51
  8. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/elements.pyi +3 -3
  9. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/figure.py +14 -2
  10. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/figure.pyi +7 -1
  11. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/plots.py +14 -2
  12. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/plots.pyi +4 -2
  13. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/texts.py +2 -2
  14. {tikzplot42-0.4.3 → tikzplot42-0.4.5/src/tikzplot42.egg-info}/PKG-INFO +1 -1
  15. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/SOURCES.txt +2 -0
  16. tikzplot42-0.4.5/tests/test12.py +32 -0
  17. tikzplot42-0.4.5/tests/test13.py +28 -0
  18. tikzplot42-0.4.3/src/tikzplot/colors.py +0 -60
  19. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/LICENSE +0 -0
  20. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/README.md +0 -0
  21. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/setup.cfg +0 -0
  22. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/__init__.py +0 -0
  23. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/__init__.pyi +0 -0
  24. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/axes3d.pyi +0 -0
  25. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/colorbar.py +0 -0
  26. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/colorbar.pyi +0 -0
  27. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/config.py +0 -0
  28. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/config.pyi +0 -0
  29. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/latex_special.py +0 -0
  30. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/py.typed +0 -0
  31. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/state.py +0 -0
  32. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/styles.py +0 -0
  33. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot/styles.pyi +0 -0
  34. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/dependency_links.txt +0 -0
  35. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/requires.txt +0 -0
  36. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/top_level.txt +0 -0
  37. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test1.py +0 -0
  38. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test10.py +0 -0
  39. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test11.py +0 -0
  40. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test2.py +0 -0
  41. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test3.py +0 -0
  42. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test4.py +0 -0
  43. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test5.py +0 -0
  44. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test6.py +0 -0
  45. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test7.py +0 -0
  46. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test8.py +0 -0
  47. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test9.py +0 -0
  48. {tikzplot42-0.4.3 → tikzplot42-0.4.5}/tests/test_styles.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tikzplot42
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: TikzPlot to make TikZ/PGFPlots plots with matplotlib.pyplot-like syntax.
5
5
  Author-email: Zan Ambrozic <zan.ambrozic7@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tikzplot42"
7
- version = "0.4.3"
7
+ version = "0.4.5"
8
8
  description = "TikzPlot to make TikZ/PGFPlots plots with matplotlib.pyplot-like syntax."
9
9
  authors = [
10
10
  { name = "Zan Ambrozic", email = "zan.ambrozic7@gmail.com" }
@@ -1,3 +1,6 @@
1
+ from typing import Any
2
+ from xml.dom import XMLNS_NAMESPACE
3
+
1
4
  import numpy as _np
2
5
  import matplotlib.pyplot as _plt
3
6
 
@@ -7,6 +10,7 @@ from .config import TikzConfig
7
10
  from .colorbar import Colorbar
8
11
  from .state import _next_imshow_num, main_name
9
12
  from .latex_special import tex_text
13
+ from .colors import _tex_color
10
14
 
11
15
  class BaseAxes:
12
16
  def __init__(self):
@@ -22,17 +26,20 @@ class BaseAxes:
22
26
  self._axis_args.add(f"/pgf/number format/.cd, 1000 sep={{{TikzConfig.THOUSANDS_SEP}}}")
23
27
 
24
28
  self._add_legend = []
29
+ self._legend_lab_col: Any = None
25
30
  self._coordinates = {}
26
31
  self._cmap_bar = None
27
32
 
28
33
  self._ext_ymin = False
29
34
  self._ext_ymax = False
30
35
 
31
- def _plot(self, x, y, settings=[], xerr=None, yerr=None, **style):
36
+ self._reverse_elements = []
37
+
38
+ def _plot(self, x, y, settings={}, xerr=None, yerr=None, **style):
32
39
  if isinstance(self, Axes) and self._polar:
33
40
  x = _np.rad2deg(x)
34
41
  e = Graph(self, (x, y), settings, xerr=xerr, yerr=yerr, **style)
35
- if TikzConfig.USE_GROUPPLOTS and ("axvspan" == settings or "axhspan" == settings):
42
+ if TikzConfig.USE_GROUPPLOTS and ("axvspan" in settings or "axhspan" in settings):
36
43
  self._elements.insert(0, e)
37
44
  else:
38
45
  self._elements.append(e)
@@ -88,7 +95,7 @@ class BaseAxes:
88
95
  self._cmap_bar = kwargs["cmap"]
89
96
  except: pass
90
97
 
91
- return self._plot(x, y, **kwargs, ls="", settings=["scatter"])
98
+ return self._plot(x, y, **kwargs, ls="", settings={"scatter": None})
92
99
 
93
100
  def semilogy(self, x, y, *args, **kwargs):
94
101
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "marker", "markersize", "ms", "label"}
@@ -126,9 +133,9 @@ class BaseAxes:
126
133
  if o == "horizontal":
127
134
  vert = False
128
135
  if vert:
129
- return self._plot(x,y,settings=["ycomb"], **kwargs)
136
+ return self._plot(x,y,settings={"ycomb": None}, **kwargs)
130
137
  else:
131
- return self._plot(y,x,settings=["xcomb"], **kwargs)
138
+ return self._plot(y,x,settings={"xcomb": None}, **kwargs)
132
139
 
133
140
  def fill_between(self, x, y1, y2=None, **kwargs):
134
141
  kws = {"fmt", "alpha", "color", "c", "label"}
@@ -166,7 +173,7 @@ class BaseAxes:
166
173
  self._plot(xs,ys,path_name=name2, alpha=0)
167
174
  else:
168
175
  name2 = inst
169
- e = Graph(self, f"fill between [of={name1} and {name2}]",settings=[], xerr=None, yerr=None, **kwargs)
176
+ e = Graph(self, f"fill between [of={name1} and {name2}]",settings={}, xerr=None, yerr=None, **kwargs)
170
177
  self._elements.append(e)
171
178
  return e
172
179
 
@@ -215,41 +222,142 @@ class BaseAxes:
215
222
  self._plot([xs[i]]*2, [ymins[i], ymaxs[i]], None, None, None, c=colorss[i], ls=lss[i])
216
223
 
217
224
  def hist(self, x, bins=10, density=False,**kwargs):
218
- #kws = {"alpha", "color", "c", "label"}
219
- #kwargs = self._check_kwargs("hist", kws, **kwargs)
220
- try:
221
- iter(x)
222
- iter(x[0])
223
- datasets = x
224
- except:
225
- datasets = [x]
225
+ kws = {"alpha", "color", "c", "label", "facecolor", "fc", "edgecolor", "ec", "orientation", "rwidth", "cumulative", "range", "histtype", "weights", "cumulative", "align", "stacked", "fill"}
226
+ kwargs = self._check_kwargs("hist", kws, **kwargs)
227
+ if isinstance(x, (list, tuple)) and len(x) > 0 and isinstance(x[0], (list, tuple, _np.ndarray)):
228
+ try:
229
+ datasets = [_np.asarray(ds, dtype=_np.float64) for ds in x]
230
+ if any(ds.ndim != 1 for ds in datasets):
231
+ raise ValueError("Nested datasets must all be 1-dimensional.")
232
+ except (ValueError, TypeError):
233
+ x_arr = _np.asarray(x)
234
+ if x_arr.ndim == 2:
235
+ datasets = [x_arr[:, i] for i in range(x_arr.shape[1])]
236
+ else:
237
+ raise ValueError("Invalid dataset structure.")
238
+ else:
239
+ x_arr = _np.asarray(x)
240
+ if x_arr.ndim == 1:
241
+ datasets = [x_arr]
242
+ elif x_arr.ndim == 2:
243
+ datasets = [x_arr[:, i] for i in range(x_arr.shape[1])]
244
+ else:
245
+ raise ValueError(f"Input must be 1D or 2D, got {x_arr.ndim}D.")
246
+
247
+ stack = kwargs.pop("stacked", False)
248
+ datas = {}
249
+ for kw in ["color", "c", "facecolor", "fc", "edgecolor", "ec", "label"]:
250
+ if kw in kwargs:
251
+ if isinstance(kwargs[kw], (list)):
252
+ if len(kwargs[kw]) != len(datasets):
253
+ raise Warning(f"Length of {kw} does not match number of datasets.")
254
+ prop = kwargs.pop(kw)
255
+ else:
256
+ prop = [kwargs.pop(kw)] * len(datasets)
257
+ for i in range(len(datasets)):
258
+ if i not in datas:
259
+ datas[i] = {}
260
+ datas[i][kw] = prop[i]
226
261
  all_data = _np.concatenate(datasets)
227
- edges = _np.histogram_bin_edges(all_data, bins=bins)
262
+ edges = _np.histogram_bin_edges(all_data, bins=bins, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
228
263
  widths = edges[1:] - edges[:-1]
229
- settings = []
264
+ offset = 0
265
+ settings = {}
266
+ hist_type = kwargs.pop("histtype", "bar")
267
+ if hist_type not in ["bar", "barstacked", "step", "stepfilled"]:
268
+ raise Warning(f"Invalid histtype: {hist_type}.")
230
269
  if "orientation" in kwargs and kwargs["orientation"] == "horizontal":
231
- settings.append("xbar")
270
+ settings["xbar"] = None
232
271
  else:
233
- settings.append("ybar")
234
- settings.append("fill")
235
- if "rwidth" in kwargs:
236
- settings.append(f"bar width={widths.mean()*kwargs['rwidth']}")
272
+ settings["ybar"] = None
273
+ if ("rwidth" in kwargs or (len(datasets) > 1 and not stack)) and isinstance(bins, int):
274
+ if stack or len(datasets) == 1:
275
+ settings["bar width"] = f"{widths.mean()*kwargs['rwidth']}"
276
+ else:
277
+ if "rwidth" in kwargs:
278
+ settings["bar width"] = f"{widths.mean()*kwargs['rwidth']/(len(datasets)+1)}"
279
+ offset = widths.mean() * kwargs['rwidth'] / (len(datasets) + 1)
280
+ else:
281
+ settings["bar width"] = f"{widths.mean()/(len(datasets)+1)}"
282
+ offset = widths.mean() / (len(datasets) + 1)
237
283
  else:
238
- settings[0] += " interval"
284
+ if "xbar" in settings:
285
+ settings.pop("xbar")
286
+ settings["xbar interval"] = None
287
+ elif "ybar" in settings:
288
+ settings.pop("ybar")
289
+ settings["ybar interval"] = None
239
290
  if "range" in kwargs:
240
- if settings[0] == "xbar":
291
+ if "xbar" in settings:
241
292
  self.set_ylim(kwargs["range"])
242
293
  elif isinstance(self, Axes):
243
294
  self.set_xlim(kwargs["range"])
244
295
  elif isinstance(self, Secondary):
245
296
  self._primary.set_xlim(kwargs["range"])
297
+ base_settings = settings.copy()
246
298
  outputs = []
247
- for data in datasets:
248
- counts, _ = _np.histogram(data, edges, density=density)
249
- centers = (edges[:-1] + edges[1:]) / 2
299
+ old_counts = _np.zeros(len(edges), dtype=_np.float64)
300
+ totals, _ = _np.histogram(all_data, edges, density=False, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
301
+ tot_sum = totals.sum()
302
+ for i in range(len(datasets)):
303
+ data = datasets[i]
304
+ settings = base_settings.copy()
305
+ kws = datas.get(i, {})
306
+ if "label" in kws:
307
+ if "xbar" in settings or "xbar interval" in settings:
308
+ settings["xbar legend"] = None
309
+ else:
310
+ settings["ybar legend"] = None
311
+ else:
312
+ settings["forget plot"] = None
313
+ fill = kws.pop("facecolor", kws.pop("fc", kws.pop("color", kws.pop("c", None))))
314
+ draw = kws.pop("edgecolor", kws.pop("ec", None))
315
+ if fill:
316
+ fill = self._match_color(fill)
317
+ settings["fill"] = fill
318
+ if draw and hist_type != "stepfilled":
319
+ draw = self._match_color(draw)
320
+ settings["draw"] = draw
321
+ else:
322
+ settings["draw"] = "none"
323
+ counts, _ = _np.histogram(data, edges, density=density, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
324
+ if "xbar interval" in settings or "ybar interval" in settings: # edges for interval
325
+ if "rwidth" in kwargs:
326
+ xs = [edges[0] + widths[0]*(1-kwargs["rwidth"])/2]
327
+ new_counts = []
328
+ for j in range(len(widths)): # Changed 'i' to 'j' to avoid overwriting dataset index 'i'
329
+ xs.append(xs[-1] + widths[j]*kwargs["rwidth"])
330
+ new_counts.append(counts[j])
331
+ if j < len(counts)-1:
332
+ xs.append(xs[-1] + (widths[j] + widths[j+1])*(1-kwargs["rwidth"])/2)
333
+ new_counts.append(0)
334
+ counts = new_counts + [0]
335
+ else:
336
+ xs = edges
337
+ counts = _np.concatenate([counts, [0]])
338
+ else: # centers
339
+ xs = (edges[:-1] + edges[1:]) / 2
340
+ if offset > 0:
341
+ settings["bar shift"] = f"{offset*(i - len(datasets)/2 + 0.5)}"
250
342
  if "cumulative" in kwargs and kwargs["cumulative"]:
251
343
  counts = _np.cumsum(counts)
252
- outputs.append(self._plot(centers, counts, settings=settings, **kwargs))
344
+ if stack:
345
+ if density:
346
+ set_counts, _ = _np.histogram(data, edges, density=False, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
347
+ counts = counts * _np.sum(set_counts) / tot_sum
348
+ counts = _np.asarray(counts, dtype=_np.float64)
349
+ counts += old_counts
350
+ old_counts = counts.copy()
351
+ if hist_type == "step":
352
+ kwargs.pop("facecolor", None)
353
+ kwargs.pop("fc", None)
354
+ kwargs.pop("fill", None)
355
+ self.step([edges[0]] + list(edges), [0] + list(counts), where="pre", **kws)
356
+ else:
357
+ e = self._plot(xs, counts, settings=settings, **kws)
358
+ outputs.append(e)
359
+ if outputs and stack:
360
+ self._reverse_elements.append([id(e) for e in outputs])
253
361
  return outputs
254
362
 
255
363
  def step(self, x, y, *args, **kwargs):
@@ -257,7 +365,7 @@ class BaseAxes:
257
365
  kwargs = self._check_kwargs("step", kws, **kwargs)
258
366
  WHERE_DICT = {"pre": "left", "post": "right", "mid": "mid"}
259
367
  where = WHERE_DICT.get(kwargs.pop("where", "pre"), None)
260
- settings = [f"const plot mark {where}"]
368
+ settings = {f"const plot mark {where}": None}
261
369
  if len(args) == 1:
262
370
  kwargs["fmt"] = args[0]
263
371
  return self._plot(x,y,settings=settings, **kwargs)
@@ -266,7 +374,7 @@ class BaseAxes:
266
374
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
267
375
  kwargs = self._check_kwargs("axvline", kws, **kwargs)
268
376
  self._ext_ymin = self._ext_ymax = True
269
- self._plot(x, (ymin, ymax), settings="axvline", **kwargs)
377
+ self._plot(x, (ymin, ymax), settings={"axvline": None}, **kwargs)
270
378
 
271
379
  def axhline(self, y, xmin=0, xmax=1, **kwargs):
272
380
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
@@ -275,7 +383,7 @@ class BaseAxes:
275
383
  self._primary._ext_xmin = self._primary._ext_xmax = True
276
384
  else:
277
385
  self._ext_xmin = self._ext_xmax = True
278
- self._plot((xmin, xmax), y, settings="axhline", **kwargs)
386
+ self._plot((xmin, xmax), y, settings={"axhline": None}, **kwargs)
279
387
 
280
388
  def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
281
389
  kws = {"c", "color", "alpha", "label"}
@@ -283,7 +391,7 @@ class BaseAxes:
283
391
  self._ext_ymin = self._ext_ymax = True
284
392
  if TikzConfig.USE_GROUPPLOTS:
285
393
  self._axis_args.add("set layers")
286
- self._plot([xmin, xmax], [ymin, ymax], settings="axvspan", **kwargs)
394
+ self._plot([xmin, xmax], [ymin, ymax], settings={"axvspan": None}, **kwargs)
287
395
 
288
396
  def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
289
397
  kws = {"c", "color", "alpha", "label"}
@@ -294,7 +402,7 @@ class BaseAxes:
294
402
  self._ext_xmin = self._ext_xmax = True
295
403
  if TikzConfig.USE_GROUPPLOTS:
296
404
  self._axis_args.add("set layers")
297
- self._plot([xmin, xmax], [ymin, ymax], settings="axhspan", **kwargs)
405
+ self._plot([xmin, xmax], [ymin, ymax], settings={"axhspan": None}, **kwargs)
298
406
 
299
407
  def set_ylabel(self, label):
300
408
  self._axis_options["ylabel"] = f"{{{tex_text(label)}}}"
@@ -354,7 +462,19 @@ class BaseAxes:
354
462
  _LEGEND_LOC_MAP = ["best", "upper right", "upper left", "lower_left", "lower right", "right", "center left", "center right", "lower center", "upper center", "center"]
355
463
  _ANCHOR_MAP = {"top": "north", "bottom": "south", "upper": "north", "lower": "south", "left": "west", "right": "east", "center": "center"}
356
464
 
465
+ def _match_color(self, input):
466
+ if isinstance(self, Axes) or isinstance(self, Secondary):
467
+ if input == "none":
468
+ return "none"
469
+ ccode, op = _tex_color(input, self._style)
470
+ if isinstance(ccode, str):
471
+ return ccode
472
+ r,g,b = ccode
473
+ self._add_col(r,g,b)
474
+ return f"c{r:.3f}{g:.3f}{b:.3f}".replace(".", "")
475
+
357
476
  def legend(self, *args, **kwargs):
477
+ legend_string = {}
358
478
  if "loc" in kwargs:
359
479
  loc = kwargs["loc"]
360
480
  lx = ly = posit = None
@@ -381,20 +501,34 @@ class BaseAxes:
381
501
  elif "east" in posit:
382
502
  lx = 1 - TikzConfig.LEGEND_REL_X
383
503
 
384
- legend_string = []
385
504
  if lx is not None and ly is not None:
386
- legend_string.append(r"at={(" + f"{lx},{ly}" + r")}")
505
+ legend_string["at"] = "{(" + f"{lx},{ly}" + r")}"
387
506
  if posit is not None and len(posit):
388
- legend_string.append(r"anchor=" + posit)
389
- if "legend style" in self._axis_options:
390
- self._axis_options["legend style"] = self._axis_options["legend style"].rstrip("}") + f" ,{','.join(legend_string)}}}"
391
- else:
392
- self._axis_options["legend style"] = f"{{{','.join(legend_string)}}}"
507
+ legend_string["anchor"] = posit
508
+
509
+ if "facecolor" in kwargs and (isinstance(self, Axes) or isinstance(self, Secondary)):
510
+ ccode = self._match_color(kwargs["facecolor"])
511
+ if ccode is not None:
512
+ legend_string["fill"] = ccode
513
+ if "edgecolor" in kwargs and (isinstance(self, Axes) or isinstance(self, Secondary)):
514
+ ccode = self._match_color(kwargs["edgecolor"])
515
+ if ccode is not None:
516
+ legend_string["draw"] = ccode
517
+ if "labelcolor" in kwargs and (isinstance(self, Axes) or isinstance(self, Secondary)):
518
+ ccode = self._match_color(kwargs["labelcolor"])
519
+ if ccode is not None:
520
+ self._legend_lab_col = ccode
521
+ if "frameon" in kwargs and not kwargs["frameon"]:
522
+ legend_string["draw"] = "none"
523
+ if "legend style" in self._axis_options:
524
+ self._axis_options["legend style"].update(legend_string)
525
+ else:
526
+ self._axis_options["legend style"] = legend_string
393
527
  self._legend_on = True
394
528
  if "ncols" in kwargs:
395
529
  self._axis_options["legend columns"] = kwargs["ncols"]
396
530
  if len(args) == 2:
397
- self._add_legend = args
531
+ self._add_legend = list(args)
398
532
  elif len(args) == 1:
399
533
  labs = args[0]
400
534
  if len(labs) > len(self._elements):
@@ -433,16 +567,36 @@ class BaseAxes:
433
567
  return ""
434
568
  for i in range(len(axs)):
435
569
  output += f"\n\\addlegendimage{{{axs[i]._style_string()}}}"
436
- output += f"\n\\addlegendentry{{{tex_text(labs[i])}}}"
570
+ if self._legend_lab_col:
571
+ output += f"\n\\addlegendentry[{self._legend_lab_col}]{{{tex_text(labs[i])}}}"
572
+ else:
573
+ output += f"\n\\addlegendentry{{{tex_text(labs[i])}}}"
437
574
  return output
438
575
 
439
576
  def _content_tex(self, filename):
440
- ouptut = "\n".join(e._to_tex(filename) for e in self._elements)
441
- ouptut += self._add_legend_entries()
577
+ element_strings = {id(e): e._to_tex(filename, self._legend_lab_col) for e in self._elements}
578
+ seq_map = {seq[0]: seq for seq in self._reverse_elements}
579
+ output_list = []
580
+ visited = set()
581
+ for e in self._elements:
582
+ e_id = id(e)
583
+ if e_id in visited:
584
+ continue
585
+ if e_id in seq_map:
586
+ seq = seq_map[e_id]
587
+ for el in reversed(seq):
588
+ output_list.append(element_strings[el])
589
+ visited.add(el)
590
+ else:
591
+ output_list.append(element_strings[e_id])
592
+ visited.add(e_id)
593
+
594
+ output = "\n".join(output_list)
595
+ output += self._add_legend_entries()
442
596
  for coord in self._coordinates:
443
597
  x,y = self._coordinates[coord]
444
- ouptut += f"\n\\coordinate ({coord}) at ({x},{y});"
445
- return ouptut
598
+ output += f"\n\\coordinate ({coord}) at ({x},{y});"
599
+ return output
446
600
 
447
601
  def _get_hard_range(self,which):
448
602
  arg = f"{which[0]}mode"
@@ -519,6 +673,10 @@ class BaseAxes:
519
673
  if attr in kwargs:
520
674
  defined[attr](kwargs.pop(attr))
521
675
 
676
+ def _reoreder_last_elements(self, n):
677
+ if n <= 0: return
678
+ self._elements = self._elements[:-n] + self._elements[:-n-1:-1]
679
+
522
680
  class Axes(BaseAxes):
523
681
 
524
682
  def __init__(self, nrows, ncols, index, fig, polar):
@@ -654,7 +812,7 @@ class Axes(BaseAxes):
654
812
  if kwargs:
655
813
  accepted_kwargs = {"color", "c", "linestyle", "ls", "linewidth", "lw", "alpha"}
656
814
  kwargs = self._check_kwargs("grid", accepted_kwargs, **kwargs)
657
- g = Graph(self, None, [], None, None, **kwargs)._style_string()
815
+ g = Graph(self, None, {}, None, None, **kwargs)._style_string()
658
816
  self._axis_options[f"{selector}grid style"] = f"{{{g}}}"
659
817
 
660
818
  def set_minorticks_num(self, num):
@@ -733,7 +891,7 @@ class Axes(BaseAxes):
733
891
  _plt.axis("off")
734
892
  _plt.imshow(*args, **kwargs)
735
893
  im_name = f"{str(main_name()[1]).removesuffix('.py')}_{TikzConfig.IMSHOW_SAVENAME}{_next_imshow_num()}.pdf"
736
- _plt.savefig(im_name,bbox_inches='tight', pad_inches=0)
894
+ _plt.savefig(im_name, bbox_inches='tight', pad_inches=0)
737
895
  return im_name
738
896
 
739
897
  def _axis_option_string(self):
@@ -754,7 +912,7 @@ class Axes(BaseAxes):
754
912
  if "extent" in self._imshow[1]:
755
913
  bounds = self._imshow[1]["extent"]
756
914
  xm, xM, ym, yM = bounds
757
- self._elements.insert(0, Graph(self, f"graphics [xmin={xm}, xmax={xM}, ymin={ym}, ymax={yM}] {{{im_name}}}",settings=[], xerr=None, yerr=None, onlayer="axis background"))
915
+ self._elements.insert(0, Graph(self, f"graphics [xmin={xm}, xmax={xM}, ymin={ym}, ymax={yM}] {{{im_name}}}", settings={}, xerr=None, yerr=None, onlayer="axis background"))
758
916
  axis_opt_str = ""
759
917
  if self._axis_args:
760
918
  axis_opt_str += ",\n".join(self._axis_args)
@@ -787,7 +945,13 @@ class Axes(BaseAxes):
787
945
  self._axis_options["ymax"] = self._fig._next_limname("ymax", self._axis_options.get("ymax", yM))
788
946
  if self._axis_options:
789
947
  if axis_opt_str: axis_opt_str += ",\n"
790
- axis_opt_str += ",\n".join(f"{k}={v}" for k, v in self._axis_options.items())
948
+ for k, v in self._axis_options.items():
949
+ if isinstance(v, dict):
950
+ axis_opt_str += f"{k}={{"
951
+ axis_opt_str += ",\n".join(f"{kk}={vv}" for kk, vv in v.items())
952
+ axis_opt_str += "},\n"
953
+ else:
954
+ axis_opt_str += f"{k}={v},\n"
791
955
  if self._colorbar:
792
956
  axis_opt_str += self._colorbar
793
957
  elif self._cmap_bar:
@@ -859,6 +1023,15 @@ class Axes(BaseAxes):
859
1023
  defined[attr](kwargs.pop(attr))
860
1024
 
861
1025
  super().set(**kwargs)
1026
+
1027
+ def set_facecolor(self, color):
1028
+ ccode, _ = _tex_color(color, self._style)
1029
+ if isinstance(ccode, str):
1030
+ self._axis_options["axis background/.style"] = f"{{fill={ccode}}}"
1031
+ else:
1032
+ r,g,b = ccode
1033
+ self._add_col(r,g,b)
1034
+ self._axis_options["axis background/.style"] = f"{{fill=c{r:.3f}{g:.3f}{b:.3f}}}".replace(".", "")
862
1035
 
863
1036
  class Secondary(BaseAxes):
864
1037
  def __init__(self, primary):
@@ -900,7 +1073,13 @@ class Secondary(BaseAxes):
900
1073
  self._axis_options["xmax"] = self._primary._axis_options["xmax"]
901
1074
  if self._axis_options:
902
1075
  if axis_opt_str: axis_opt_str += ",\n"
903
- axis_opt_str += ",\n".join(f"{k}={v}" for k, v in self._axis_options.items())
1076
+ for k, v in self._axis_options.items():
1077
+ if isinstance(v, dict):
1078
+ axis_opt_str += f"{k}={{"
1079
+ axis_opt_str += ",\n".join(f"{kk}={vv}" for kk, vv in v.items())
1080
+ axis_opt_str += "},\n"
1081
+ else:
1082
+ axis_opt_str += f"{k}={v},\n"
904
1083
  return axis_opt_str
905
1084
 
906
1085
  def _padding(self):
@@ -1,6 +1,5 @@
1
1
  from typing import Any, Optional, Sequence, Tuple, Union, Literal
2
2
  import numpy as np
3
- from pyrsistent import T
4
3
  from .colorbar import Colorbar
5
4
 
6
5
  ArrayLike = Union[Sequence[float], np.ndarray]
@@ -277,15 +276,24 @@ class BaseAxes:
277
276
  def hist(
278
277
  self,
279
278
  x: Union[ArrayLike, Sequence[ArrayLike]],
280
- bins: int = ...,
281
- density: bool = ...,
279
+ bins: int | Sequence[float] = 10,
282
280
  *,
283
- cumulative: bool = ...,
281
+ weight: ArrayLike | None = None,
282
+ density: bool = False,
283
+ cumulative: bool = False,
284
+ histtype: Literal["bar", "barstacked", "step", "stepfilled"] = "bar",
284
285
  orientation: Literal["horizontal","vertical"] = "vertical",
285
- rwidth: Optional[float] = ...,
286
- range: Optional[Tuple[float,float]] = ...,
287
- color: Optional[ColorLike] = ...,
288
- **kwargs: Any
286
+ rwidth: float | None = None,
287
+ range: Tuple[float,float] | None = None,
288
+ color: ColorLike = ...,
289
+ facecolor: ColorLike = ...,
290
+ fc: ColorLike = ...,
291
+ edgecolor: ColorLike = ...,
292
+ ec: ColorLike = ...,
293
+ align: Literal["left", "mid", "right"] = ...,
294
+ stacked: bool = False,
295
+ fill: bool = True,
296
+ #**kwargs: Any
289
297
  ) -> None:
290
298
  """
291
299
  Draw histogram to the selected axis.
@@ -482,7 +490,7 @@ class BaseAxes:
482
490
  Set y-axis tick labels.
483
491
  """
484
492
  ...
485
- def legend(self, *args: Any, loc: Optional[Union[int,str,Tuple[float,float]]] = ...) -> None:
493
+ def legend(self, *args: Any, loc: Optional[Union[int,str,Tuple[float,float]]] = ..., facecolor: Optional[ColorLike] = ..., edgecolor: Optional[ColorLike] = ..., labelcolor: Optional[ColorLike] = ..., frameon: Optional[bool] = ...) -> None:
486
494
  """
487
495
  Show legend for the selected axis. Despite arguments requires at least one plotted element on the axis (not necesarily with label) to show up (LaTeX does not allow legend on empty axis).
488
496
 
@@ -510,7 +518,7 @@ class BaseAxes:
510
518
  def _get_limit(self, which: Literal["xmin","xmax","ymin","ymax"]) -> Tuple[float,bool,str]: ...
511
519
  def _set_range(self, which: Literal["xmin","xmax","ymin","ymax"], value: Union[float, int]): ...
512
520
  def _num_points(self) -> list[int]: ...
513
- # def _add_color(self, ?) -> None: ...
521
+ def _add_col(self, r: float, g: float, b: float) -> None: ...
514
522
 
515
523
  class Axes(BaseAxes):
516
524
  def __init__(self, nrows: int, ncols: int, index: int, fig: Any, pol: bool) -> None: ...
@@ -663,6 +671,16 @@ class Axes(BaseAxes):
663
671
  Initialize secondary y-axis.
664
672
  """
665
673
  ...
674
+ def set_facecolor(self, color: ColorLike) -> None:
675
+ """
676
+ Set axis background color.
677
+
678
+ Parameters
679
+ ----------
680
+ color: all matplotlib color formats (without X11/xkcd)
681
+ Background color of axis: RGB/RGBA (tuple), HEX (str), grayscale (float), single-char (str), name (str), default cycle ("CX", X int), none for invisible
682
+ """
683
+ ...
666
684
  def _export_imshow(self, *args: Any, **kwargs: Any) -> str: ...
667
685
  def _axis_options_string(self) -> str: ...
668
686
  def _margins(self) -> tuple[float, float, float, float]: ...
@@ -74,7 +74,7 @@ class Axes3:
74
74
  if _add_settgs is not None:
75
75
  self._axis_options = _add_settgs | self._axis_options
76
76
 
77
- def _plot(self, xs, ys, zs, zdir="z", settings=[], xerr=None, yerr=None, zerr=None, **style):
77
+ def _plot(self, xs, ys, zs, zdir="z", settings={}, xerr=None, yerr=None, zerr=None, **style):
78
78
  if isinstance(zs, (float,int)):
79
79
  zs = [zs] * len(xs)
80
80
  if zdir == "y":
@@ -110,12 +110,12 @@ class Axes3:
110
110
  def plot_surface(self, X, Y, Z, **kwargs):
111
111
  kws = {"alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
112
112
  kwargs = self._check_kwargs("plot", kws, **kwargs)
113
- return self._plot(X, Y, Z, settings=["surf", f"mesh/rows={X.shape[0]}"] , **kwargs)
113
+ return self._plot(X, Y, Z, settings={"surf": None, "mesh/rows": X.shape[0]} , **kwargs)
114
114
 
115
115
  def plot_wireframe(self, X, Y, Z, **kwargs):
116
116
  kws = {"alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
117
117
  kwargs = self._check_kwargs("plot", kws, **kwargs)
118
- return self._plot(X, Y, Z, settings=["mesh", f"mesh/rows={X.shape[0]}"] , **kwargs)
118
+ return self._plot(X, Y, Z, settings={"mesh": None, "mesh/rows": X.shape[0]} , **kwargs)
119
119
 
120
120
  def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, **kwargs):
121
121
  kws = {"fmt", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "marker", "markersize", "ms", "label"}
@@ -171,7 +171,7 @@ class Axes3:
171
171
  self._plot(xs,ys,path_name=name2, alpha=0)
172
172
  else:
173
173
  name2 = inst"""
174
- e = Graph3(self, f"fill between [of={name1} and {name2}]",settings=[], xerr=None, yerr=None, zerr=None, **kwargs)
174
+ e = Graph3(self, f"fill between [of={name1} and {name2}]",settings={}, xerr=None, yerr=None, zerr=None, **kwargs)
175
175
  self._elements.append(e)
176
176
  return e
177
177