tikzplot42 0.6.2__tar.gz → 0.6.4__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 (64) hide show
  1. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/PKG-INFO +2 -1
  2. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/README.md +1 -0
  3. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/pyproject.toml +1 -1
  4. tikzplot42-0.6.4/src/tikzplot/arrows.py +87 -0
  5. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/axes.py +172 -60
  6. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/axes.pyi +148 -40
  7. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/axes3d.py +23 -9
  8. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/axes3d.pyi +6 -0
  9. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/elements.py +13 -4
  10. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/figure.py +10 -5
  11. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/figure.pyi +116 -9
  12. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/latex_special.py +1 -1
  13. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/plots.py +6 -1
  14. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/plots.pyi +234 -37
  15. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/texts.py +3 -3
  16. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot42.egg-info/PKG-INFO +2 -1
  17. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot42.egg-info/SOURCES.txt +1 -0
  18. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test16.py +1 -1
  19. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test5.py +1 -1
  20. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test7.py +1 -0
  21. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/LICENSE +0 -0
  22. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/setup.cfg +0 -0
  23. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/__init__.py +0 -0
  24. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/__init__.pyi +0 -0
  25. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/border_finder.py +0 -0
  26. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/colorbar.py +0 -0
  27. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/colorbar.pyi +0 -0
  28. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/colors.py +0 -0
  29. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/config.py +0 -0
  30. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/config.pyi +0 -0
  31. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/elements.pyi +0 -0
  32. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/pie.py +0 -0
  33. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/py.typed +0 -0
  34. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/state.py +0 -0
  35. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/styles.py +0 -0
  36. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot/styles.pyi +0 -0
  37. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot42.egg-info/dependency_links.txt +0 -0
  38. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot42.egg-info/requires.txt +0 -0
  39. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/src/tikzplot42.egg-info/top_level.txt +0 -0
  40. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test1.py +0 -0
  41. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test10.py +0 -0
  42. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test11.py +0 -0
  43. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test12.py +0 -0
  44. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test13.py +0 -0
  45. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test14.py +0 -0
  46. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test15.py +0 -0
  47. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test17.py +0 -0
  48. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test18.py +0 -0
  49. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test19.py +0 -0
  50. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test2.py +0 -0
  51. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test20.py +0 -0
  52. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test21.py +0 -0
  53. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test22.py +0 -0
  54. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test23.py +0 -0
  55. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test24.py +0 -0
  56. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test25.py +0 -0
  57. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test26.py +0 -0
  58. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test27.py +0 -0
  59. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test28.py +0 -0
  60. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test3.py +0 -0
  61. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test4.py +0 -0
  62. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test6.py +0 -0
  63. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test8.py +0 -0
  64. {tikzplot42-0.6.2 → tikzplot42-0.6.4}/tests/test9.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tikzplot42
3
- Version: 0.6.2
3
+ Version: 0.6.4
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
@@ -764,6 +764,7 @@ Some basic plot commands are already implemented with commonly used arguments:
764
764
  - `imshow()`,
765
765
  - `matshow()`,
766
766
  - `text()`,
767
+ - `annotate()`,
767
768
  - `bxp()` (without notches),
768
769
  - `boxplot()` (without notches),
769
770
  - `violin()`,
@@ -64,6 +64,7 @@ Some basic plot commands are already implemented with commonly used arguments:
64
64
  - `imshow()`,
65
65
  - `matshow()`,
66
66
  - `text()`,
67
+ - `annotate()`,
67
68
  - `bxp()` (without notches),
68
69
  - `boxplot()` (without notches),
69
70
  - `violin()`,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tikzplot42"
7
- version = "0.6.2"
7
+ version = "0.6.4"
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" }
@@ -0,0 +1,87 @@
1
+ from .colors import _tex_color
2
+
3
+ class Arrow:
4
+ _LINE_MAP = {"--": "dashed", ":": "dotted", "-.": "dashdotted", "-":"solid"}
5
+ def __init__(self, ax, x1, y1, x2, y2, coordinate1, coordinate2, **kwargs):
6
+ self._axes = ax
7
+ self._x1 = x1
8
+ self._y1 = y1
9
+ self._x2 = x2
10
+ self._y2 = y2
11
+ self._coordinate1 = coordinate1
12
+ self._coordinate2 = coordinate2
13
+ self._kwargs = kwargs
14
+ self._opacity = 1
15
+ self._visible = True
16
+
17
+ def match_color(self, input):
18
+ self._has_color = True
19
+ ccode, op = _tex_color(input)
20
+ if not isinstance(op, bool):
21
+ self._opacity = op
22
+ if isinstance(ccode, str):
23
+ return ccode
24
+ r,g,b=ccode
25
+ self._axes._add_col(r,g,b)
26
+ return f"c{r:.3f}{g:.3f}{b:.3f}".replace(".", "")
27
+
28
+ def _style_string(self):
29
+ output = []
30
+ if "arrowstyle" in self._kwargs:
31
+ allowed = {"<", ">", "|", "stealth", "latex"}
32
+ arstyle = self._kwargs["arrowstyle"]
33
+ arstyle = arstyle.split("-")
34
+ if not all(s in allowed for s in arstyle):
35
+ raise ValueError(f"Arrow style {self._kwargs['arrowstyle']} not recognized. Valid options should consist of: {allowed}, separated by -.")
36
+ output.append(self._kwargs["arrowstyle"])
37
+ if "color" in self._kwargs or "c" in self._kwargs:
38
+ self._color = self.match_color(self._kwargs.get("color") or self._kwargs.get("c"))
39
+ if "alpha" in self._kwargs:
40
+ self._opacity = self._kwargs["alpha"]
41
+ if "line width" in self._kwargs or "lw" in self._kwargs:
42
+ output.append("line width=" + str(self._kwargs.get("line width") or self._kwargs.get("lw")))
43
+ if "linestyle" in self._kwargs or "ls" in self._kwargs:
44
+ ls = self._kwargs.get("linestyle") or self._kwargs.get("ls")
45
+ if ls in self._LINE_MAP:
46
+ ls = self._LINE_MAP[ls]
47
+ elif ls not in self._LINE_MAP.values():
48
+ raise ValueError(f"Line style {ls} not recognized. Valid options are: {list(self._LINE_MAP.keys())}.")
49
+ output.append("line style=" + str(ls))
50
+ if self._opacity < 1:
51
+ output.insert(0,f"opacity={self._opacity}")
52
+ if self._color:
53
+ output.insert(0,f"color={self._color}")
54
+ return ", ".join(output)
55
+
56
+ def _to_tex(self, *args, **kwargs):
57
+ if self._visible:
58
+ if self._coordinate1 is not None:
59
+ return f"\\coordinate {self._coordinate1} at (axis cs:{self._x1},{self._y1});"
60
+ else:
61
+ return ""
62
+
63
+ def _to_tex_fin(self):
64
+ if self._visible:
65
+ if self._coordinate1 is not None and self._coordinate2 is not None:
66
+ return f"\\draw[{self._style_string()}] {self._coordinate2} -- {self._coordinate1};"
67
+ else:
68
+ return ""
69
+
70
+ def _filter(self, which, label):
71
+ pass
72
+
73
+ def _get_erange(self, which):
74
+ if which == "xmin":
75
+ return min(self._x1, self._x2)
76
+ elif which == "xmax":
77
+ return max(self._x1, self._x2)
78
+ elif which == "ymin":
79
+ return min(self._y1, self._y2)
80
+ elif which == "ymax":
81
+ return max(self._y1, self._y2)
82
+
83
+ def _num_points(self):
84
+ return 2
85
+
86
+ def _reduce_points(self, max_points):
87
+ pass
@@ -9,6 +9,7 @@ from skimage import measure
9
9
 
10
10
  from .elements import Graph, Single
11
11
  from .texts import Text
12
+ from .arrows import Arrow
12
13
  from .config import TikzConfig
13
14
  from .colorbar import Colorbar
14
15
  from .state import _next_imshow_num, main_name
@@ -538,6 +539,7 @@ class BaseAxes:
538
539
  else:
539
540
  lines[0].append(list(zip(x_seg, y_seg)))
540
541
  ol = None
542
+ settings["empty line"] = "jump"
541
543
  if cmap is not None:
542
544
  vmin = 0
543
545
  vmax = _np.max(_np.hypot(u, v))
@@ -546,7 +548,6 @@ class BaseAxes:
546
548
  self._get_free_overlay(cb=cb)
547
549
  ol = self._get_overlay()
548
550
  self._cmap_bar[ol] = cb
549
- settings["empty line"] = "jump"
550
551
  settings["mesh"] = None
551
552
  settings["point meta"] = "explicit"
552
553
  st["color"] = True
@@ -591,7 +592,7 @@ class BaseAxes:
591
592
  arrow_u.append(pu / p_norm)
592
593
  arrow_v.append(pv / p_norm)
593
594
  arrow_c.append(p_norm)
594
- sc = 1e-5 * max(domain_dx, domain_dy) / _np.max(_np.hypot(u, v))
595
+ sc = 1e-2 * max(domain_dx, domain_dy)
595
596
  if len(arrow_x) > 0:
596
597
  if cmap is not None:
597
598
  self.quiver(_np.array(arrow_x), _np.array(arrow_y), _np.array(arrow_u), _np.array(arrow_v), _np.array(arrow_c), scale=sc, pivot="middle", lw=arrowsize/2, cmap=cmap, vmin=0, vmax=_np.max(_np.hypot(u, v)))
@@ -1331,7 +1332,7 @@ class BaseAxes:
1331
1332
  overlay = self._get_free_overlay()
1332
1333
  self._overlay_special[overlay] = {"stack plots": "y", "area style": None}
1333
1334
  if base_set != "zero":
1334
- self._plot(x, baseline, overlay=overlay, alpha=0)
1335
+ self._plot(x, baseline, overlay=overlay, alpha=0, settings={"stackplot": {}})
1335
1336
  datas = {}
1336
1337
  for kw in ["labels", "colors", "alpha", "facecolor", "fc", "edgecolor", "ec", "linewidth", "lw", "linestyle", "ls", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"]:
1337
1338
  if kw in kwargs:
@@ -1351,7 +1352,7 @@ class BaseAxes:
1351
1352
  for i in range(len(y)):
1352
1353
  kws = {kw: datas[kw][i] for kw in datas}
1353
1354
  kws["_endnotes"] = r"\closedcycle"
1354
- output.append(self._plot(x, y[i], overlay=overlay, settings={"fill": None}, **kws))
1355
+ output.append(self._plot(x, y[i], overlay=overlay, settings={"fill": None, "stackplot": {}}, **kws))
1355
1356
  return output
1356
1357
 
1357
1358
  def step(self, x, y, *args, **kwargs):
@@ -1810,7 +1811,7 @@ class BaseAxes:
1810
1811
  if "fontsize" in kwargs:
1811
1812
  st["font"] = self._tex_fontsize(kwargs["fontsize"])
1812
1813
  if st:
1813
- self._update_axis_options("y tick style", st)
1814
+ self._update_axis_options("y tick label style", st)
1814
1815
  passing = {k: kwargs[k] for k in pass_to_labels if k in kwargs}
1815
1816
  if passing:
1816
1817
  self.set_yticklabels(**passing)
@@ -1869,7 +1870,7 @@ class BaseAxes:
1869
1870
  elif ha != "center":
1870
1871
  raise Warning(f"Invalid horizontalalignment: {ha}. Must be one of 'left', 'right', or 'center'.")
1871
1872
  if st:
1872
- self._update_axis_options("y tick style", st)
1873
+ self._update_axis_options("y tick label style", st)
1873
1874
  if list(labels) != []:
1874
1875
  if len(labels) == 1 and isinstance(labels[0], (list, tuple)):
1875
1876
  if len(labels[0]) > 0:
@@ -2549,7 +2550,7 @@ class BaseAxes:
2549
2550
  kws = {"alpha", "color", "c", "fontsize", "on_top", "size", "backgroundcolor", "horizontalalignment", "ha", "verticalalignment", "va", "rotation", "label"}
2550
2551
  kwargs = self._check_kwargs("text", kws, **kwargs)
2551
2552
  if "fontsize" in kwargs or "size" in kwargs:
2552
- kwargs["fontsize"] = kwargs.pop("size", kwargs.pop("fontsize"))
2553
+ kwargs["fontsize"] = self._tex_fontsize(kwargs.pop("size", kwargs.pop("fontsize")))
2553
2554
  on_top = kwargs.pop("on_top", True)
2554
2555
  if on_top:
2555
2556
  assert self._fig is not None
@@ -2560,6 +2561,32 @@ class BaseAxes:
2560
2561
  txt = Text(self, x, y, None, s, **kwargs)
2561
2562
  self._elements[self._get_overlay()].append(txt)
2562
2563
 
2564
+ def annotate(self, text, xy, xytext=None, arrowprops=None, **kwargs):
2565
+ kws = {"alpha", "color", "c", "fontsize", "size", "backgroundcolor", "horizontalalignment", "ha", "verticalalignment", "va", "rotation", "label"}
2566
+ kwargs = self._check_kwargs("annotate", kws, **kwargs)
2567
+ akws = {"arrowstyle", "alpha", "color", "c", "linewidth", "lw", "linestyle", "ls"}
2568
+ passing_kwargs = {"alpha", "color", "c"}
2569
+ ap0 = {k: kwargs[k] for k in passing_kwargs if k in kwargs}
2570
+ if arrowprops is not None:
2571
+ arrowprops = self._check_kwargs("annotate: arrowprops", akws, **arrowprops)
2572
+ else:
2573
+ arrowprops = {}
2574
+ arrowprops = ap0 | arrowprops
2575
+ assert self._fig is not None
2576
+ xa, ya = xy
2577
+ if xytext is not None:
2578
+ xt, yt = xytext
2579
+ else:
2580
+ return self.text(xa, ya, text, **kwargs)
2581
+ ca = self._fig._next_coordinate_name()
2582
+ ct = self._fig._next_coordinate_name()
2583
+ txt = Text(self, xt, yt, ct, text, **kwargs)
2584
+ self._fig._add_text(txt)
2585
+ arr = Arrow(self, xa, ya, xt, yt, ca, ct, **arrowprops)
2586
+ self._fig._add_arrow(arr)
2587
+ self._elements[self._get_overlay()].append(txt)
2588
+ self._elements[self._get_overlay()].append(arr)
2589
+
2563
2590
  def magnify(self, x_p, y_p, x_m, y_m, zoom, size, **kwargs):
2564
2591
  kws = {"shape", "connect"}
2565
2592
  kwargs = self._check_kwargs("magnify", kws, **kwargs)
@@ -2568,6 +2595,9 @@ class BaseAxes:
2568
2595
  self._coordinates.update({f"spypoint{n}": (x_p,y_p)})
2569
2596
  self._coordinates.update({f"spyviewr{n}": (x_m,y_m)})
2570
2597
 
2598
+ def set_axis_off(self):
2599
+ self._axis_options["hide axis"] = None
2600
+
2571
2601
  def _add_legend_entries(self):
2572
2602
  if self._add_legend == []: return ""
2573
2603
  axs, labs = self._add_legend
@@ -2681,14 +2711,6 @@ class BaseAxes:
2681
2711
  for e in self._get_all_elements():
2682
2712
  e._filter(which, value)
2683
2713
 
2684
- def _num_points(self):
2685
- return [e._num_points() for e in self._get_all_elements()]
2686
-
2687
- def _reduce_points(self, limit):
2688
- for e in self._get_all_elements():
2689
- if e._num_points() > 0:
2690
- e._reduce_points(limit)
2691
-
2692
2714
  def _add_col(self, r,g,b):
2693
2715
  assert self._fig is not None
2694
2716
  self._fig._add_col(r,g,b)
@@ -3073,10 +3095,15 @@ class Axes(BaseAxes):
3073
3095
  self._elements[0].insert(0, Graph(self, f"graphics [xmin={xm}, xmax={xM}, ymin={ym}, ymax={yM}] {{{im_name}}}", settings={}, xerr=None, yerr=None, onlayer="axis background"))
3074
3096
  if self._hidable and self._get_all_elements() == [] and self._secondary_y is None:
3075
3097
  self._axis_options["hide axis"] = None
3076
- axis_opt_str = ""
3077
- auxiliary_opt_str = ""
3098
+ axis_opt_str: dict[int, str] = {0: "", 1: "", 2: "", 3: ""} # 0 - lower, 1 - any, 2 - top, 3 - single
3099
+ def _add_to_ax_opt(which: int|Iterable[int], entry:str):
3100
+ if isinstance(which, int):
3101
+ axis_opt_str[which] += entry
3102
+ else:
3103
+ for i in which:
3104
+ axis_opt_str[i] += entry
3078
3105
  if TikzConfig.SCHOOL_AXIS:
3079
- axis_opt_str += f",\n axis lines=middle,\n xlabel style={{at={{(ticklabel* cs:{1+TikzConfig.SCHOOL_AXIS_LABEL_MARGIN})}},anchor=north}},\n ylabel style={{at={{(ticklabel* cs:{1+TikzConfig.SCHOOL_AXIS_LABEL_MARGIN})}},anchor=east}}"
3106
+ axis_opt_str[1] += f",\n axis lines=middle,\n xlabel style={{at={{(ticklabel* cs:{1+TikzConfig.SCHOOL_AXIS_LABEL_MARGIN})}},anchor=north}},\n ylabel style={{at={{(ticklabel* cs:{1+TikzConfig.SCHOOL_AXIS_LABEL_MARGIN})}},anchor=east}}"
3080
3107
  if ("xmin" in self._axis_options and self._axis_options["xmin"] == 0) or ("xmax" in self._axis_options and self._axis_options["xmax"] == 0):
3081
3108
  self._axis_options["extra x ticks"] = r"{0}"
3082
3109
  if ("ymin" in self._axis_options and self._axis_options["ymin"] == 0) or ("ymax" in self._axis_options and self._axis_options["ymax"] == 0):
@@ -3085,16 +3112,16 @@ class Axes(BaseAxes):
3085
3112
  if "set layers" in self._axis_args:
3086
3113
  self._axis_args.remove("set layers")
3087
3114
  if TikzConfig.SCHOOL_AXIS:
3088
- axis_opt_str += f",\n set layers,\n axis line style={{on layer=axis foreground}}"
3089
- auxiliary_opt_str += ",\n set layers"
3115
+ axis_opt_str[2] += f",\n set layers,\n axis line style={{on layer=axis foreground}}"
3116
+ _add_to_ax_opt((0,1), ",\n set layers")
3090
3117
  else:
3091
3118
  if self._get_all_elements() != []:
3092
- axis_opt_str += f",\nset layers=standard, cell picture=true, grid style={{on layer=axis grid}}"
3093
- auxiliary_opt_str += "set layers=standard"
3119
+ axis_opt_str[2] += f",\nset layers=standard, cell picture=true, grid style={{on layer=axis grid}}"
3120
+ _add_to_ax_opt((0,1), "set layers=standard")
3094
3121
  if self._axis_args and not (self._hidable and self._get_all_elements() == [] and self._secondary_y is None):
3095
- axis_opt_str = ",\n".join(self._axis_args) + axis_opt_str
3122
+ axis_opt_str[2] = ",\n".join(self._axis_args) + axis_opt_str[2]
3096
3123
  if "set layers" in self._axis_args:
3097
- auxiliary_opt_str += ",\n set layers"
3124
+ _add_to_ax_opt((0,1), ",\n set layers")
3098
3125
  if self._ext_xmin or self._ext_xmax:
3099
3126
  lower = self._get_range("xmin")
3100
3127
  upper = self._get_range("xmax")
@@ -3126,16 +3153,24 @@ class Axes(BaseAxes):
3126
3153
  if self._int_ymax is not None and (upper[0] is None or upper[0] <= self._int_ymax):
3127
3154
  self._axis_options["ymax"] = self._int_ymax
3128
3155
  if self._axis_options:
3129
- if axis_opt_str: axis_opt_str += ",\n"
3130
- if auxiliary_opt_str: auxiliary_opt_str += ",\n"
3156
+ axis_opt_str[3] = axis_opt_str[2]
3157
+ for k in axis_opt_str:
3158
+ if axis_opt_str[k] and not axis_opt_str[k].endswith(",\n"):
3159
+ axis_opt_str[k] += ",\n"
3131
3160
  for k, v in self._axis_options.items():
3132
3161
  if v != {}:
3133
3162
  entry = self._parse_entry(k,v)
3134
- axis_opt_str += entry + ",\n"
3163
+ axis_opt_str[3] += entry + ",\n"
3164
+ to_back = ["grid"]
3165
+ if any(q in k for q in to_back):
3166
+ axis_opt_str[0] += entry + ",\n"
3167
+ else:
3168
+ axis_opt_str[2] += entry + ",\n"
3135
3169
  if k in ["xmin", "xmax", "ymin", "ymax", "xmode", "ymode", "log basis x", "log basis y", "width", "height", "at"]:
3136
- auxiliary_opt_str += entry + ",\n"
3137
- axis_opt_str = axis_opt_str.removesuffix(",,\n")
3138
- auxiliary_opt_str = auxiliary_opt_str.removesuffix(",,\n")
3170
+ _add_to_ax_opt((0,1), entry + ",\n")
3171
+ axis_opt_str[0] += "axis line style={draw=none},\n tick style={draw=none},\n minor tick style={draw=none},\n xticklabels={},\n yticklabels={},\n"
3172
+ for k in axis_opt_str:
3173
+ axis_opt_str[k] = axis_opt_str[k].removesuffix(",,\n").strip("\n")
3139
3174
  adds = {}
3140
3175
  for c in self._elements.keys():
3141
3176
  adds[c] = ""
@@ -3145,7 +3180,8 @@ class Axes(BaseAxes):
3145
3180
  cm = self._cmap_bar[c]
3146
3181
  assert isinstance(cm, Colorbar)
3147
3182
  adds[c] += f"colormap={cm._generate_tex_colormap(cm._cmap)},\n"
3148
- return axis_opt_str, "hide axis,\n" + auxiliary_opt_str, alias, adds
3183
+ axis_opt_str[1] = "hide axis,\n" + axis_opt_str[1]
3184
+ return axis_opt_str, alias, adds
3149
3185
 
3150
3186
  def _parse_entry(self, k, v):
3151
3187
  if v is None:
@@ -3170,12 +3206,16 @@ class Axes(BaseAxes):
3170
3206
  self._virtual = True
3171
3207
  from .border_finder import _get_sizes
3172
3208
  self._ext_xmin = self._ext_xmax = self._ext_ymin = self._ext_ymax = True
3173
- main, aux, alias, adds = self._axis_option_string()
3174
- if 0 in adds:
3209
+ main, alias, adds = self._axis_option_string()
3210
+ aux = main[1]
3211
+ main = main[2]
3212
+ if not self._fig._tight_params.get("ignore_colorbars", False) and 0 in adds:
3175
3213
  main += adds[0]
3176
3214
  main = r"""\begin{axis}[""" + main + r"]" + r"\end{axis}" + "\n"
3177
3215
  if self._secondary_y is not None:
3178
- main2, aux2 = self._secondary_y._axis_option_string()
3216
+ main2 = self._secondary_y._axis_option_string()
3217
+ aux2 = main2[1]
3218
+ main2 = main2[2]
3179
3219
  main2 = r"""\begin{axis}[""" + main2 + r"]" + r"\end{axis}" + "\n"
3180
3220
  main += main2
3181
3221
  for k in adds:
@@ -3253,12 +3293,18 @@ class Axes(BaseAxes):
3253
3293
  self._fig._add_required_package("\\usepgfplotslibrary{polar}")
3254
3294
  elif self._projection == "smith":
3255
3295
  self._fig._add_required_package("\\usepgfplotslibrary{smithchart}")
3256
- main_ax, aux_ax, alias, adds = self._axis_option_string()
3296
+ ax_opt, alias, adds = self._axis_option_string()
3297
+ aux_ax = ax_opt[1]
3298
+ if len (self._elements) <= 1:
3299
+ main_ax = ax_opt[3]
3300
+ else:
3301
+ main_ax = ax_opt[2]
3257
3302
  contents = self._content_tex(filename)
3258
3303
  if self._extend is not None:
3259
3304
  lines.append(f"\\nextgroupplot[alias={alias}, width={self._extend[0]}cm, height={self._extend[1]}cm, hide axis]")
3260
3305
  for i in self._elements.keys():
3261
- spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(i, {}).items()]) + ",\n" if i in self._overlay_special else ""
3306
+ spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(i, {}).items()]) + "," if i in self._overlay_special else ""
3307
+ spec = spec.strip("\n")
3262
3308
  if self._projection == "polar":
3263
3309
  lines2.append("\\begin{polaraxis}["+ f"\n at={{({alias}.south west)}},")
3264
3310
  elif self._projection == "smith":
@@ -3268,9 +3314,18 @@ class Axes(BaseAxes):
3268
3314
  if TikzConfig.SCALE_ONLY_AXIS:
3269
3315
  lines2.append("scale only axis,")
3270
3316
  if i == self._get_overlay():
3271
- lines2.append(f"{main_ax}{spec}\n]")
3317
+ if i in adds and adds[i] != "":
3318
+ addit = adds[i]
3319
+ addit = addit.replace(f"at={{({alias}", f"at={{({alias}a")
3320
+ addit = f"alias={alias}a,\n" + addit
3321
+ lines2.append(f"{main_ax}{spec+addit}\n]")
3322
+ else:
3323
+ lines2.append(f"{main_ax}{spec}\n]")
3272
3324
  else:
3273
- lines2.append(f"{aux_ax}{spec}\n]")
3325
+ if i == 0:
3326
+ lines2.append(f"{ax_opt[0]}{spec}\n]")
3327
+ else:
3328
+ lines2.append(f"{aux_ax}{spec}\n]")
3274
3329
  lines2.append(contents[i])
3275
3330
  if i == self._get_overlay() and self._overlay_legend:
3276
3331
  lines2 += self._overlay_legend_entries
@@ -3284,19 +3339,28 @@ class Axes(BaseAxes):
3284
3339
  else:
3285
3340
  lines2.append("\\end{axis}")
3286
3341
  if self._secondary_y is not None:
3287
- main_ax2, aux_ax2 = self._secondary_y._axis_option_string()
3342
+ ax_opt2 = self._secondary_y._axis_option_string()
3343
+ aux_ax2 = ax_opt2[1]
3344
+ if len (self._secondary_y._elements) <= 1:
3345
+ main_ax2 = ax_opt2[3]
3346
+ else:
3347
+ main_ax2 = ax_opt2[2]
3288
3348
  contents2 = self._secondary_y._content_tex(filename)
3289
3349
  for i in self._secondary_y._elements.keys():
3290
3350
  lines2.append("\\begin{axis}[")
3291
3351
  if TikzConfig.SCALE_ONLY_AXIS:
3292
3352
  lines2.append("scale only axis,")
3293
- spec = ",\n".join([self._parse_entry(k, v) for k, v in self._secondary_y._overlay_special.get(i, {}).items()]) + ",\n" if i in self._secondary_y._overlay_special else ""
3353
+ spec = ",\n".join([self._parse_entry(k, v) for k, v in self._secondary_y._overlay_special.get(i, {}).items()]) + "," if i in self._secondary_y._overlay_special else ""
3294
3354
  if i in adds:
3295
3355
  spec += adds[i]
3356
+ spec = spec.strip("\n")
3296
3357
  if i == sorted(self._secondary_y._elements.keys())[-1]:
3297
3358
  lines2.append(f"{main_ax2}{spec}\n]")
3298
3359
  else:
3299
- lines2.append(f"{aux_ax2}{spec}\n]")
3360
+ if i == 0:
3361
+ lines2.append(f"{ax_opt2[0]}{spec}\n]")
3362
+ else:
3363
+ lines2.append(f"{aux_ax2}{spec}\n]")
3300
3364
  lines2.append(contents2[i])
3301
3365
  if i == sorted(self._secondary_y._elements.keys())[-1]:
3302
3366
  lines2 += self._secondary_y._overlay_legend_entries
@@ -3307,9 +3371,10 @@ class Axes(BaseAxes):
3307
3371
  elif self._projection in ["polar", "smith"] and TikzConfig.USE_GROUPPLOTS and not single:
3308
3372
  lines.append(f"\\nextgroupplot[alias={alias}, width={self._width}, height={self._height}, hide axis]")
3309
3373
  for i in self._elements.keys():
3310
- spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(i, {}).items()]) + ",\n" if i in self._overlay_special else ""
3374
+ spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(i, {}).items()]) + "," if i in self._overlay_special else ""
3311
3375
  if i in adds:
3312
3376
  spec += adds[i]
3377
+ spec = spec.strip("\n")
3313
3378
  if self._projection == "polar":
3314
3379
  lines2.append("\\begin{polaraxis}["+ f"\n at={{({alias}.south west)}},")
3315
3380
  elif self._projection == "smith":
@@ -3341,28 +3406,31 @@ class Axes(BaseAxes):
3341
3406
  if TikzConfig.SCALE_ONLY_AXIS:
3342
3407
  lines.append("scale only axis,")
3343
3408
  if self._get_overlay() == 0:
3344
- spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(0, {}).items()]) + ",\n" if 0 in self._overlay_special else ""
3409
+ spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(0, {}).items()]) + "," if 0 in self._overlay_special else ""
3345
3410
  if 0 in adds:
3346
3411
  spec += adds[0]
3412
+ spec = spec.strip("\n")
3347
3413
  if self._secondary_y is not None or any([c is not None for c in self._colorbar.values()]) or self._externs:
3348
3414
  lines.append(f"{main_ax}{spec}alias={alias}\n]")
3349
3415
  else:
3350
3416
  lines.append(f"{main_ax}{spec}\n]")
3351
3417
  lines.append(contents[0])
3352
3418
  else:
3353
- spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(0, {}).items()]) + ",\n" if 0 in self._overlay_special else ""
3419
+ spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(0, {}).items()]) + "," if 0 in self._overlay_special else ""
3354
3420
  if 0 in adds:
3355
3421
  spec += adds[0]
3422
+ spec = spec.strip("\n")
3356
3423
  if len(contents[0]) == 0:
3357
- lines.append(f"{aux_ax}{spec}alias={alias}\n]".replace("set layers=standard,\n", ""))
3424
+ lines.append(f"{ax_opt[0]}{spec}alias={alias}\n]".replace("set layers=standard,\n", ""))
3358
3425
  else:
3359
- lines.append(f"{aux_ax}{spec}alias={alias}\n]")
3426
+ lines.append(f"{ax_opt[0]}{spec}alias={alias}\n]")
3360
3427
  lines.append(contents[0])
3361
3428
  for i in self._elements.keys():
3362
3429
  if i == 0: continue
3363
- spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(i, {}).items()]) + ",\n" if i in self._overlay_special else ""
3430
+ spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(i, {}).items()]) + "," if i in self._overlay_special else ""
3364
3431
  if i in adds:
3365
3432
  spec += adds[i]
3433
+ spec = spec.strip("\n")
3366
3434
  lines2.append("\\begin{axis}[")
3367
3435
  if TikzConfig.SCALE_ONLY_AXIS:
3368
3436
  lines2.append("scale only axis,")
@@ -3390,17 +3458,26 @@ class Axes(BaseAxes):
3390
3458
  elif not TikzConfig.USE_GROUPPLOTS or (TikzConfig.USE_GROUPPLOTS and single):
3391
3459
  lines.append("\\end{axis}")
3392
3460
  if self._secondary_y is not None:
3393
- main_ax2, aux_ax2 = self._secondary_y._axis_option_string()
3461
+ ax_opt2 = self._secondary_y._axis_option_string()
3462
+ aux_ax2 = ax_opt2[1]
3463
+ if len (self._secondary_y._elements) <= 1:
3464
+ main_ax2 = ax_opt2[3]
3465
+ else:
3466
+ main_ax2 = ax_opt2[2]
3394
3467
  contents2 = self._secondary_y._content_tex(filename)
3395
3468
  for i in self._secondary_y._elements.keys():
3396
3469
  lines2.append("\\begin{axis}[")
3397
3470
  if TikzConfig.SCALE_ONLY_AXIS:
3398
3471
  lines2.append("scale only axis,")
3399
- spec = ",\n".join([self._parse_entry(k, v) for k, v in self._secondary_y._overlay_special.get(i, {}).items()]) + ",\n" if i in self._secondary_y._overlay_special else ""
3472
+ spec = ",\n".join([self._parse_entry(k, v) for k, v in self._secondary_y._overlay_special.get(i, {}).items()]) + "," if i in self._secondary_y._overlay_special else ""
3473
+ spec = spec.strip("\n")
3400
3474
  if i == sorted(self._secondary_y._elements.keys())[-1]:
3401
3475
  lines2.append(f"{main_ax2}{spec}\n]")
3402
3476
  else:
3403
- lines2.append(f"{aux_ax2}{spec}\n]")
3477
+ if i == 0:
3478
+ lines2.append(f"{ax_opt2[0]}{spec}\n]")
3479
+ else:
3480
+ lines2.append(f"{aux_ax2}{spec}\n]")
3404
3481
  lines2.append(contents2[i])
3405
3482
  if i == sorted(self._secondary_y._elements.keys())[-1]:
3406
3483
  lines2 += self._secondary_y._overlay_legend_entries
@@ -3433,6 +3510,18 @@ class Axes(BaseAxes):
3433
3510
  if position not in ["above", "below", "left", "right"]:
3434
3511
  raise ValueError(f"Invalid position: {position}. Must be one of 'above', 'below', 'left', or 'right'.")
3435
3512
  return self._fig._inset(self, position, relsize=relsize, sharex=sharex, sharey=sharey)
3513
+
3514
+ def _num_points(self):
3515
+ if self._secondary_y is not None:
3516
+ return [e._num_points() for e in self._get_all_elements()] + self._secondary_y._num_points()
3517
+ return [e._num_points() for e in self._get_all_elements()]
3518
+
3519
+ def _reduce_points(self, limit):
3520
+ for e in self._get_all_elements():
3521
+ if e._num_points() > 0:
3522
+ e._reduce_points(limit)
3523
+ if self._secondary_y is not None:
3524
+ self._secondary_y._reduce_points(limit)
3436
3525
 
3437
3526
  class Secondary(BaseAxes):
3438
3527
  def __init__(self, primary):
@@ -3459,11 +3548,10 @@ class Secondary(BaseAxes):
3459
3548
  self._axis_options["width"] = self._primary._width
3460
3549
  if self._primary._height:
3461
3550
  self._axis_options["height"] = self._primary._height
3462
- axis_opt_str = ""
3463
- auxiliary_opt_str = ""
3551
+ axis_opt_str: dict[int, str] = {0: "", 1: "", 2: "", 3: ""} # 0 - lower, 1 - any, 2 - top, 3 - single
3464
3552
  if self._axis_args:
3465
- axis_opt_str += ",\n".join(self._axis_args)
3466
- auxiliary_opt_str += ",\n".join(self._axis_args)
3553
+ for i in [1,2,3]:
3554
+ axis_opt_str[i] += ",\n".join(self._axis_args)
3467
3555
  if self._ext_ymin or self._ext_ymax:
3468
3556
  lower = self._get_range("ymin")
3469
3557
  upper = self._get_range("ymax")
@@ -3474,9 +3562,15 @@ class Secondary(BaseAxes):
3474
3562
  self._axis_options["ymax"] = self._fig._next_limname("ymax", self._axis_options.get("ymax", yM))
3475
3563
  self._axis_options["xmin"] = self._primary._axis_options["xmin"]
3476
3564
  self._axis_options["xmax"] = self._primary._axis_options["xmax"]
3565
+ if "xmode" in self._primary._axis_options:
3566
+ self._axis_options["xmode"] = self._primary._axis_options["xmode"]
3567
+ if "log basis x" in self._primary._axis_options:
3568
+ self._axis_options["log basis x"] = self._primary._axis_options["log basis x"]
3477
3569
  if self._axis_options:
3478
- if axis_opt_str: axis_opt_str += ",\n"
3479
- if auxiliary_opt_str: auxiliary_opt_str += ",\n"
3570
+ axis_opt_str[3] = axis_opt_str[2]
3571
+ for k in axis_opt_str:
3572
+ if axis_opt_str[k] and not axis_opt_str[k].endswith(",\n"):
3573
+ axis_opt_str[k] += ",\n"
3480
3574
  def parse_entry(k,v):
3481
3575
  if isinstance(v, dict):
3482
3576
  return f"{k}={{" + ",\n".join(f"{kk}={vv}" for kk, vv in v.items()) + "}"
@@ -3484,10 +3578,20 @@ class Secondary(BaseAxes):
3484
3578
  return f"{k}={v}"
3485
3579
  for k, v in self._axis_options.items():
3486
3580
  entry = parse_entry(k,v)
3487
- axis_opt_str += entry + ",\n"
3581
+ axis_opt_str[3] += entry + ",\n"
3582
+ to_back = ["grid"]
3583
+ if any(q in k for q in to_back):
3584
+ axis_opt_str[0] += entry + ",\n"
3585
+ else:
3586
+ axis_opt_str[2] += entry + ",\n"
3488
3587
  if k in ["xmin", "xmax", "ymin", "ymax", "xmode", "ymode", "log basis x", "log basis y", "width", "height", "at", "axis y line*", "axis x line", "anchor"]:
3489
- auxiliary_opt_str += entry + ",\n"
3490
- return axis_opt_str, "hide axis,\n" + auxiliary_opt_str
3588
+ axis_opt_str[0] += entry + ",\n"
3589
+ axis_opt_str[1] += entry + ",\n"
3590
+ axis_opt_str[0] += "axis line style={draw=none},\n tick style={draw=none},\n minor tick style={draw=none},\n xticklabels={},\n yticklabels={},\n"
3591
+ for k in axis_opt_str:
3592
+ axis_opt_str[k] = axis_opt_str[k].removesuffix(",,\n").strip("\n")
3593
+ axis_opt_str[1] = "hide axis,\n" + axis_opt_str[1]
3594
+ return axis_opt_str
3491
3595
 
3492
3596
  def _padding(self):
3493
3597
  return TikzConfig.SEC_Y_PADDING + TikzConfig.YTICK_PADDING * self._yticks + TikzConfig.SEC_Y_LABEL_PADDING * ("ylabel" in self._axis_options)
@@ -3496,4 +3600,12 @@ class Secondary(BaseAxes):
3496
3600
  return self._primary._get_defcol(index)
3497
3601
 
3498
3602
  def _get_index(self):
3499
- return self._primary._get_index()
3603
+ return self._primary._get_index()
3604
+
3605
+ def _num_points(self):
3606
+ return [e._num_points() for e in self._get_all_elements()]
3607
+
3608
+ def _reduce_points(self, limit):
3609
+ for e in self._get_all_elements():
3610
+ if e._num_points() > 0:
3611
+ e._reduce_points(limit)