tikzplot42 0.5.0__tar.gz → 0.5.1__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 (49) hide show
  1. {tikzplot42-0.5.0/src/tikzplot42.egg-info → tikzplot42-0.5.1}/PKG-INFO +5 -3
  2. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/README.md +4 -2
  3. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/pyproject.toml +1 -1
  4. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/axes.py +347 -21
  5. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/axes.pyi +160 -8
  6. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/elements.py +55 -2
  7. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/elements.pyi +2 -1
  8. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/plots.py +7 -0
  9. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/plots.pyi +128 -25
  10. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/styles.py +11 -8
  11. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/texts.py +1 -6
  12. {tikzplot42-0.5.0 → tikzplot42-0.5.1/src/tikzplot42.egg-info}/PKG-INFO +5 -3
  13. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot42.egg-info/SOURCES.txt +1 -2
  14. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test11.py +7 -3
  15. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test14.py +3 -2
  16. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test15.py +5 -2
  17. tikzplot42-0.5.0/tests/test_styles.py +0 -17
  18. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/LICENSE +0 -0
  19. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/setup.cfg +0 -0
  20. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/__init__.py +0 -0
  21. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/__init__.pyi +0 -0
  22. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/axes3d.py +0 -0
  23. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/axes3d.pyi +0 -0
  24. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/colorbar.py +0 -0
  25. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/colorbar.pyi +0 -0
  26. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/colors.py +0 -0
  27. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/config.py +0 -0
  28. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/config.pyi +0 -0
  29. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/figure.py +0 -0
  30. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/figure.pyi +0 -0
  31. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/latex_special.py +0 -0
  32. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/py.typed +0 -0
  33. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/state.py +0 -0
  34. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot/styles.pyi +0 -0
  35. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot42.egg-info/dependency_links.txt +0 -0
  36. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot42.egg-info/requires.txt +0 -0
  37. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/src/tikzplot42.egg-info/top_level.txt +0 -0
  38. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test1.py +0 -0
  39. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test10.py +0 -0
  40. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test12.py +0 -0
  41. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test13.py +0 -0
  42. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test2.py +0 -0
  43. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test3.py +0 -0
  44. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test4.py +0 -0
  45. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test5.py +0 -0
  46. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test6.py +0 -0
  47. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test7.py +0 -0
  48. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test8.py +0 -0
  49. {tikzplot42-0.5.0 → tikzplot42-0.5.1}/tests/test9.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tikzplot42
3
- Version: 0.5.0
3
+ Version: 0.5.1
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
@@ -740,6 +740,7 @@ Some basic plot commands are already implemented with commonly used arguments:
740
740
  - `hlines()/vlines()`,
741
741
  - `hist()`,
742
742
  - `bar()/barh()`,
743
+ - `bar_label()`,
743
744
  - `step()`,
744
745
  - `imshow()`,
745
746
  - `text()`,
@@ -763,7 +764,8 @@ If axes and plot have different name for command with same effect, both are impl
763
764
  - `ax.set_xticklabels()/ax.set_yticklabels()`,
764
765
  - `legend()` (basic position control),
765
766
  - `ax.twinx()`,
766
- - `ax.set()`.
767
+ - `ax.set()`,
768
+ - `ax.tick_params()`,
767
769
 
768
770
  There is a limited support for built-in styles (e.g. `plt.style.use("default")`), you may also define your own style using `plt.style.set_profile()`. Currently supported features are: color cycle (hex colors sequence), line width (in pt), grid (with matplotlib kwargs), background (latex style key=value), additional settings (raw latex code for optional arguments to axis: key=value).
769
771
 
@@ -790,6 +792,6 @@ For plot configurations (default sizes, paddings, etc.), use `from tikzplot impo
790
792
  Currently supported parameters are listed with their default values in `src/tikzplot/config.py`.
791
793
 
792
794
  ## More features coming soon...
793
- - additional arguments supprot for implemented commands,
795
+ - additional arguments support for implemented commands,
794
796
  - improved 3D plots (currently only basic support: plot, errorbar, fill_between, scatter, plot_wireframe, plot_surface),
795
797
  - ...
@@ -41,6 +41,7 @@ Some basic plot commands are already implemented with commonly used arguments:
41
41
  - `hlines()/vlines()`,
42
42
  - `hist()`,
43
43
  - `bar()/barh()`,
44
+ - `bar_label()`,
44
45
  - `step()`,
45
46
  - `imshow()`,
46
47
  - `text()`,
@@ -64,7 +65,8 @@ If axes and plot have different name for command with same effect, both are impl
64
65
  - `ax.set_xticklabels()/ax.set_yticklabels()`,
65
66
  - `legend()` (basic position control),
66
67
  - `ax.twinx()`,
67
- - `ax.set()`.
68
+ - `ax.set()`,
69
+ - `ax.tick_params()`,
68
70
 
69
71
  There is a limited support for built-in styles (e.g. `plt.style.use("default")`), you may also define your own style using `plt.style.set_profile()`. Currently supported features are: color cycle (hex colors sequence), line width (in pt), grid (with matplotlib kwargs), background (latex style key=value), additional settings (raw latex code for optional arguments to axis: key=value).
70
72
 
@@ -91,6 +93,6 @@ For plot configurations (default sizes, paddings, etc.), use `from tikzplot impo
91
93
  Currently supported parameters are listed with their default values in `src/tikzplot/config.py`.
92
94
 
93
95
  ## More features coming soon...
94
- - additional arguments supprot for implemented commands,
96
+ - additional arguments support for implemented commands,
95
97
  - improved 3D plots (currently only basic support: plot, errorbar, fill_between, scatter, plot_wireframe, plot_surface),
96
98
  - ...
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tikzplot42"
7
- version = "0.5.0"
7
+ version = "0.5.1"
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,6 +1,6 @@
1
1
  from multiprocessing import parent_process
2
- from typing import Any
3
- from xml.dom import XMLNS_NAMESPACE
2
+ from typing import Any, Iterable
3
+ import copy
4
4
 
5
5
  import numpy as _np
6
6
  import matplotlib.pyplot as _plt
@@ -41,6 +41,7 @@ class BaseAxes:
41
41
  self._int_ymax = None
42
42
 
43
43
  self._preferred_lims = {}
44
+ self._bar_labels = {}
44
45
 
45
46
  def _get_overlay(self):
46
47
  return sorted(self._elements.keys())[-1]
@@ -70,6 +71,80 @@ class BaseAxes:
70
71
  def _check_approximate_equal(self, a, b, tol=1e-5):
71
72
  return _np.count_nonzero(_np.abs(_np.asarray(a) - _np.asarray(b))/_np.max(_np.abs(b)) > tol) == 0
72
73
 
74
+ def _update_axis_options(self, key, value):
75
+ accepted = {"label style": (dict, " "), "tick style": (dict, " "), "tick label style": (dict, " "), "tick align": (str, "")}
76
+ if isinstance(value, dict):
77
+ value = value.copy()
78
+ if key.startswith(("x", "y")):
79
+ k = key.removeprefix("x").removeprefix("y").strip()
80
+ set_ax = ("x" if key.startswith("x") else "y") + accepted[k][1]
81
+ other_ax = ("y" if set_ax.strip() == "x" else "x") + accepted[k][1]
82
+ if k not in accepted:
83
+ self._axis_options[key] = value
84
+ elif k in self._axis_options:
85
+ if accepted[k][0] != type(value):
86
+ raise ValueError(f"Value for {key} must be of type {accepted[k][0].__name__}.")
87
+ if accepted[k][0] == dict:
88
+ common_props = self._axis_options[k].copy()
89
+ setting_axis = {}
90
+ other_axis = {}
91
+ for vk in list(value.keys()).copy():
92
+ if vk in common_props:
93
+ if common_props[vk] == value[vk]:
94
+ value.pop(vk)
95
+ else:
96
+ other_axis[vk] = common_props.pop(vk)
97
+ self._axis_options[k].pop(vk)
98
+ setting_axis[vk] = value[vk]
99
+ else:
100
+ setting_axis[vk] = value[vk]
101
+ if other_axis:
102
+ if f"{other_ax}{k}" not in self._axis_options:
103
+ self._axis_options[f"{other_ax}{k}"] = {}
104
+ self._axis_options[f"{other_ax}{k}"].update(other_axis)
105
+ if setting_axis:
106
+ if f"{set_ax}{k}" not in self._axis_options:
107
+ self._axis_options[f"{set_ax}{k}"] = {}
108
+ self._axis_options[f"{set_ax}{k}"].update(setting_axis)
109
+ if common_props:
110
+ self._axis_options[k].update(common_props)
111
+ elif accepted[k][0] == str:
112
+ if self._axis_options[k] != value:
113
+ self._axis_options[f"{set_ax}{k}"] = value
114
+ self._axis_options[f"{other_ax}{k}"] = self._axis_options.pop(k)
115
+ else:
116
+ if key not in self._axis_options and accepted[k][0] == dict:
117
+ self._axis_options[key] = {}
118
+ if accepted[k][0] == dict:
119
+ self._axis_options[key].update(value)
120
+ else:
121
+ self._axis_options[key] = value
122
+ else:
123
+ if key not in accepted:
124
+ self._axis_options[key] = value
125
+ elif accepted[key][0] != type(value):
126
+ raise ValueError(f"Value for {key} must be of type {accepted[key][0].__name__}.")
127
+ else:
128
+ if "x" + accepted[key][1] + key in self._axis_options:
129
+ if accepted[key][0] == dict:
130
+ for vk in list(value.keys()).copy():
131
+ if vk in self._axis_options["x" + accepted[key][1] + key]:
132
+ self._axis_options["x" + accepted[key][1] + key].pop(vk)
133
+ elif accepted[key][0] == str:
134
+ self._axis_options.pop("x" + accepted[key][1] + key)
135
+ if "y" + accepted[key][1] + key in self._axis_options:
136
+ if accepted[key][0] == dict:
137
+ for vk in list(value.keys()).copy():
138
+ if vk in self._axis_options["y" + accepted[key][1] + key]:
139
+ self._axis_options["y" + accepted[key][1] + key].pop(vk)
140
+ elif accepted[key][0] == str:
141
+ self._axis_options.pop("y" + accepted[key][1] + key)
142
+ if key not in self._axis_options and accepted[key][0] == dict:
143
+ self._axis_options[key] = {}
144
+ if accepted[key][0] == dict:
145
+ self._axis_options[key].update(value)
146
+ self._axis_options[key] = value
147
+
73
148
  def _plot(self, x, y, settings={}, xerr=None, yerr=None, overlay=None, note=None, **style):
74
149
  spec = None
75
150
  if self._get_overlay() in self._overlay_special:
@@ -572,6 +647,44 @@ class BaseAxes:
572
647
  settings[bar_type] = None
573
648
  return self._plot(k, v, yerr=kwargs.get("yerr", None), xerr=kwargs.get("xerr", None), overlay=overlay, settings=settings, note=note, **element_args)
574
649
 
650
+ def bar_label(self, container, labels=None, **kwargs):
651
+ kws = {"color", "c", "fontsize", "rotation", "fmt", "padding"}
652
+ kwargs = self._check_kwargs("bar_label", kws, **kwargs)
653
+ if not isinstance(container, Graph):
654
+ raise Warning("Container must be a Graph object.")
655
+ if labels is not None and labels != []:
656
+ if len(labels) != len(container._x):
657
+ raise Warning("Length of labels does not match number of bars.")
658
+
659
+ if "color" in kwargs or "c" in kwargs:
660
+ c = kwargs.pop("color", kwargs.pop("c", None))
661
+ kwargs["color_parsed"] = self._match_color(c)
662
+ if "fontsize" in kwargs:
663
+ kwargs["fontsize_parsed"] = self._tex_fontsize(kwargs["fontsize"])
664
+ o = self._get_element_overlay(container)
665
+ if o not in self._bar_labels:
666
+ self._bar_labels[o] = {}
667
+ if labels != []:
668
+ if labels is None:
669
+ labels = container._y
670
+ fmt = kwargs.pop("fmt", "%g")
671
+ if not isinstance(fmt, str):
672
+ raise Warning("fmt must be a string.")
673
+ def format_label(q):
674
+ if "{" in fmt and "}" in fmt:
675
+ return fmt.format(q)
676
+ return fmt % q
677
+ labels = [format_label(q) if isinstance(q, (int, float)) else q for q in labels]
678
+ cleaned = ["".join(c for c in s if c.isdigit() or c in ".-") for s in labels]
679
+ parsed = _np.array(cleaned, dtype=float)
680
+ all_match = _np.allclose(container._y, parsed)
681
+ if all_match:
682
+ labels = None
683
+ if labels is not None and labels != []:
684
+ container._add_meta_column(labels)
685
+ self._bar_labels[self._get_element_overlay(container)][container] = kwargs | {"bar_labels": labels}
686
+
687
+
575
688
  def step(self, x, y, *args, **kwargs):
576
689
  kws = {"fmt", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "marker", "markersize", "ms", "label", "where"}
577
690
  kwargs = self._check_kwargs("step", kws, **kwargs)
@@ -616,7 +729,46 @@ class BaseAxes:
616
729
  self._axis_args.add("set layers")
617
730
  return self._plot([xmin, xmax], [ymin, ymax], settings={"axhspan": None}, **kwargs)
618
731
 
619
- def set_ylabel(self, label):
732
+ def set_ylabel(self, label, **kwargs):
733
+ kws = {"fontsize", "color", "c", "loc", "rotate"}
734
+ kwargs = self._check_kwargs("set_ylabel", kws, **kwargs)
735
+ st = {}
736
+ if "fontsize" in kwargs:
737
+ st["font"] = self._tex_fontsize(kwargs["fontsize"])
738
+ if "color" in kwargs or "c" in kwargs:
739
+ c = kwargs.get("color", kwargs.get("c", None))
740
+ st["text"] = self._match_color(c)
741
+ if "loc" in kwargs:
742
+ loc = kwargs["loc"]
743
+ if loc not in ["top", "center", "bottom"]:
744
+ raise Warning(f"Invalid loc: {loc}. Must be one of 'top', 'center', or 'bottom'.")
745
+ if loc == "top":
746
+ st["at"] = "{(yticklabel cs:1)}"
747
+ if isinstance(self, Secondary):
748
+ st["anchor"] = "south west"
749
+ else:
750
+ st["anchor"] = "south east"
751
+ elif loc == "bottom":
752
+ st["at"] = "{(yticklabel cs:0)}"
753
+ if isinstance(self, Secondary):
754
+ st["anchor"] = "south west"
755
+ else:
756
+ st["anchor"] = "south east"
757
+ else:
758
+ st["at"] = {}
759
+ st["anchor"] = {}
760
+ if "rotate" in kwargs:
761
+ if kwargs["rotate"] not in ["vertical", "horizontal"]:
762
+ raise Warning(f"Invalid rotate: {kwargs['rotate']}. Must be one of 'vertical' or 'horizontal'.")
763
+ if kwargs["rotate"] == "horizontal":
764
+ if isinstance(self, Secondary):
765
+ st["rotate"] = "-90"
766
+ else:
767
+ st["rotate"] = "-90"
768
+ else:
769
+ st["rotate"] = {}
770
+ if st:
771
+ self._update_axis_options("y label style", st)
620
772
  self._axis_options["ylabel"] = f"{{{tex_text(label)}}}"
621
773
 
622
774
  def set_ylim(self, *args, **kwargs):
@@ -651,7 +803,17 @@ class BaseAxes:
651
803
  if "base" in kwargs:
652
804
  self._axis_options["log basis y"] = kwargs["base"]
653
805
 
654
- def set_yticks(self, ticks, labels=None):
806
+ def set_yticks(self, ticks, labels=None, **kwargs):
807
+ kws = {"color", "c", "fontsize"}
808
+ kwargs = self._check_kwargs("set_yticks", kws, **kwargs)
809
+ st = {}
810
+ if "color" in kwargs or "c" in kwargs:
811
+ c = kwargs.get("color", kwargs.get("c", None))
812
+ st["text"] = self._match_color(c)
813
+ if "fontsize" in kwargs:
814
+ st["font"] = self._tex_fontsize(kwargs["fontsize"])
815
+ if st:
816
+ self._update_axis_options("y tick style", st)
655
817
  if ticks:
656
818
  s_ticks = map(str, ticks)
657
819
  self._axis_options["ytick"]=f"{{{','.join(s_ticks)}}}"
@@ -664,13 +826,78 @@ class BaseAxes:
664
826
  self._axis_options["yticks"]=r"{}"
665
827
  self._yticks = False
666
828
 
667
- def set_yticklabels(self, labels):
829
+ def set_yticklabels(self, labels, **kwargs):
830
+ kws = {"color", "c", "fontsize"}
831
+ kwargs = self._check_kwargs("set_yticklabels", kws, **kwargs)
832
+ st = {}
833
+ if "color" in kwargs or "c" in kwargs:
834
+ c = kwargs.get("color", kwargs.get("c", None))
835
+ st["text"] = self._match_color(c)
836
+ if "fontsize" in kwargs:
837
+ st["font"] = self._tex_fontsize(kwargs["fontsize"])
838
+ if st:
839
+ self._update_axis_options("y tick style", st)
668
840
  if labels:
669
841
  self._axis_options["yticklabels"]=f"{{{tex_text(','.join(labels))}}}"
670
842
  else:
671
843
  self._axis_options["yticklabels"]=r"{}"
672
844
  self._yticks = False
673
845
 
846
+ def tick_params(self, axis="both", **kwargs):
847
+ kws = {"color", "c", "labelsize", "labelcolor", "colors", "direction", "top", "bottom", "left", "right"}
848
+ kwargs = self._check_kwargs("tick_params", kws, **kwargs)
849
+ if axis not in ["x", "y", "both"]:
850
+ raise Warning(f"Invalid axis: {axis}. Must be one of 'x', 'y', or 'both'.")
851
+ if isinstance(self, Secondary):
852
+ if axis != "y":
853
+ raise Warning("tick_params only supports axis='y' for secondary axes.")
854
+ X_POS_MAP = {"top": (True, False), "bottom": (False, True), "both": (True, True), "none": (False, False)}
855
+ Y_POS_MAP = {"left": (True, False), "right": (False, True), "both": (True, True), "none": (False, False)}
856
+ xt_b, xt_t = X_POS_MAP.get(kwargs.pop("xtick pos", "both"), (True, True))
857
+ yt_l, yt_r = Y_POS_MAP.get(kwargs.pop("ytick pos", "both"), (True, True))
858
+ prefix = "x" if axis == "x" else ("y" if axis == "y" else "")
859
+ if "bottom" in kwargs:
860
+ if axis == "y":
861
+ raise Warning("Cannot set 'bottom' for y-axis.")
862
+ xt_b = kwargs.pop("bottom")
863
+ if "top" in kwargs:
864
+ if axis == "y":
865
+ raise Warning("Cannot set 'top' for y-axis.")
866
+ xt_t = kwargs.pop("top")
867
+ if "left" in kwargs:
868
+ if axis == "x":
869
+ raise Warning("Cannot set 'left' for x-axis.")
870
+ yt_l = kwargs.pop("left")
871
+ if "right" in kwargs:
872
+ if axis == "x":
873
+ raise Warning("Cannot set 'right' for x-axis.")
874
+ yt_r = kwargs.pop("right")
875
+ X_INV = {v: k for k, v in X_POS_MAP.items()}
876
+ Y_INV = {v: k for k, v in Y_POS_MAP.items()}
877
+ self._axis_options["xtick pos"] = X_INV[(xt_t, xt_b)]
878
+ self._axis_options["ytick pos"] = Y_INV[(yt_l, yt_r)]
879
+ if self._axis_options["xtick pos"] == "both":
880
+ self._axis_options.pop("xtick pos")
881
+ if "colors" in kwargs:
882
+ c = self._match_color(kwargs.pop("colors"))
883
+ self._update_axis_options(prefix + " tick style", {"draw": c})
884
+ self._update_axis_options(prefix + " tick label style", {"text": c})
885
+ if "color" in kwargs or "c" in kwargs:
886
+ c = self._match_color(kwargs.get("color", kwargs.get("c")))
887
+ self._update_axis_options(prefix + " tick style", {"draw": c})
888
+ if "labelcolor" in kwargs:
889
+ c = self._match_color(kwargs["labelcolor"])
890
+ self._update_axis_options(prefix + " tick label style", {"text": c})
891
+ if "labelsize" in kwargs:
892
+ fs = kwargs["labelsize"]
893
+ self._update_axis_options(prefix + " tick label style", {"font": self._tex_fontsize(fs)})
894
+ if "direction" in kwargs:
895
+ direction = kwargs["direction"]
896
+ if direction not in ["in", "out", "inout"]:
897
+ raise Warning(f"Invalid direction: {direction}. Must be one of 'in', 'out', or 'inout'.")
898
+ TICK_DIR_MAP = {"in": "inside", "out": "outside", "inout": "center"}
899
+ self._update_axis_options(prefix + "tick align", TICK_DIR_MAP[direction])
900
+
674
901
  _LEGEND_LOC_MAP = ["best", "upper right", "upper left", "lower_left", "lower right", "right", "center left", "center right", "lower center", "upper center", "center"]
675
902
  _ANCHOR_MAP = {"top": "north", "bottom": "south", "upper": "north", "lower": "south", "left": "west", "right": "east", "center": "center"}
676
903
  _FONT_SIZE_MAP = {"xx-small": "tiny", "x-small": "scriptsize", "small": "footnotesize", "medium": "small", "large": "normalsize", "x-large": "large", "xx-large": "Large"}
@@ -775,6 +1002,8 @@ class BaseAxes:
775
1002
  def text(self, x, y, s, **kwargs):
776
1003
  kws = {"alpha", "color", "c", "fontsize", "on_top", "size", "backgroundcolor", "horizontalalignment", "ha", "verticalalignment", "va", "rotation", "label"}
777
1004
  kwargs = self._check_kwargs("text", kws, **kwargs)
1005
+ if "fontsize" in kwargs or "size" in kwargs:
1006
+ kwargs["fontsize"] = kwargs.pop("size", kwargs.pop("fontsize"))
778
1007
  on_top = kwargs.pop("on_top", True)
779
1008
  if on_top:
780
1009
  assert self._fig is not None
@@ -959,7 +1188,7 @@ class Axes(BaseAxes):
959
1188
  _bcgnd = self._style._get_background_cycle()
960
1189
  if _bcgnd is not None:
961
1190
  self._axis_options["axis background/.style"] = f"{{{_bcgnd}}}"
962
- _add_settgs = self._style._get_additional_settings()
1191
+ _add_settgs = copy.deepcopy(self._style._get_additional_settings())
963
1192
  if _add_settgs is not None:
964
1193
  self._axis_options = _add_settgs | self._axis_options
965
1194
 
@@ -1002,10 +1231,63 @@ class Axes(BaseAxes):
1002
1231
  cmap = "viridis"
1003
1232
  return (self, cmap, m, M)
1004
1233
 
1005
- def set_xlabel(self, label):
1234
+ def set_xlabel(self, label, **kwargs):
1235
+ kws = {"fontsize", "color", "c", "loc", "rotate"}
1236
+ kwargs = self._check_kwargs("set_xlabel", kws, **kwargs)
1237
+ st = {}
1238
+ if "fontsize" in kwargs:
1239
+ st["font"] = self._tex_fontsize(kwargs["fontsize"])
1240
+ if "color" in kwargs or "c" in kwargs:
1241
+ c = kwargs.get("color", kwargs.get("c", None))
1242
+ st["text"] = self._match_color(c)
1243
+ if "loc" in kwargs:
1244
+ loc = kwargs["loc"]
1245
+ if loc not in ["left", "center", "right"]:
1246
+ raise Warning(f"Invalid loc: {loc}. Must be one of 'left', 'center', or 'right'.")
1247
+ if loc == "left":
1248
+ st["at"] = "{(xticklabel cs:0)}"
1249
+ st["anchor"] = "north west"
1250
+ elif loc == "right":
1251
+ st["at"] = "{(xticklabel cs:1)}"
1252
+ st["anchor"] = "north east"
1253
+ else:
1254
+ st["at"] = {}
1255
+ st["anchor"] = {}
1256
+ if "rotate" in kwargs:
1257
+ if kwargs["rotate"] not in ["vertical", "horizontal"]:
1258
+ raise Warning(f"Invalid rotate: {kwargs['rotate']}. Must be one of 'vertical' or 'horizontal'.")
1259
+ if kwargs["rotate"] == "vertical":
1260
+ st["rotate"] = "-90"
1261
+ else:
1262
+ st["rotate"] = {}
1263
+ if st:
1264
+ self._update_axis_options("x label style", st)
1006
1265
  self._axis_options["xlabel"] = f"{{{tex_text(label)}}}"
1007
1266
 
1008
- def set_title(self, title):
1267
+ def set_title(self, title, **kwargs):
1268
+ kws = {"fontsize", "color", "c", "loc"}
1269
+ kwargs = self._check_kwargs("set_title", kws, **kwargs)
1270
+ st = {}
1271
+ if "fontsize" in kwargs:
1272
+ st["font"] = self._tex_fontsize(kwargs["fontsize"])
1273
+ if "color" in kwargs or "c" in kwargs:
1274
+ c = kwargs.get("color", kwargs.get("c", None))
1275
+ st["text"] = self._match_color(c)
1276
+ if "loc" in kwargs:
1277
+ loc = kwargs["loc"]
1278
+ if loc not in ["left", "center", "right"]:
1279
+ raise Warning(f"Invalid loc: {loc}. Must be one of 'left', 'center', or 'right'.")
1280
+ if loc == "left":
1281
+ st["at"] = "{(0.0,1.0)}"
1282
+ st["anchor"] = "south west"
1283
+ elif loc == "right":
1284
+ st["at"] = "{(1.0,1.0)}"
1285
+ st["anchor"] = "south east"
1286
+ else:
1287
+ st["at"] = {}
1288
+ st["anchor"] = {}
1289
+ if st:
1290
+ self._update_axis_options("title style", st)
1009
1291
  self._axis_options["title"] = f"{{{tex_text(title)}}}"
1010
1292
 
1011
1293
  def grid(self, visible=True, which="major", **kwargs):
@@ -1070,7 +1352,17 @@ class Axes(BaseAxes):
1070
1352
  if "base" in kwargs:
1071
1353
  self._axis_options["log basis x"] = kwargs["base"]
1072
1354
 
1073
- def set_xticks(self, ticks, labels=None):
1355
+ def set_xticks(self, ticks, labels=None, **kwargs):
1356
+ kws = {"color", "c", "fontsize"}
1357
+ kwargs = self._check_kwargs("set_xticks", kws, **kwargs)
1358
+ st = {}
1359
+ if "color" in kwargs or "c" in kwargs:
1360
+ c = kwargs.get("color", kwargs.get("c", None))
1361
+ st["text"] = self._match_color(c)
1362
+ if "fontsize" in kwargs:
1363
+ st["font"] = self._tex_fontsize(kwargs["fontsize"])
1364
+ if st:
1365
+ self._update_axis_options("x tick style", st)
1074
1366
  if ticks:
1075
1367
  s_ticks = map(str, ticks)
1076
1368
  self._axis_options["xtick"]=f"{{{','.join(s_ticks)}}}"
@@ -1083,7 +1375,17 @@ class Axes(BaseAxes):
1083
1375
  self._axis_options["xticks"]=r"{}"
1084
1376
  self._xticks = False
1085
1377
 
1086
- def set_xticklabels(self, labels):
1378
+ def set_xticklabels(self, labels, **kwargs):
1379
+ kws = {"color", "c", "fontsize"}
1380
+ kwargs = self._check_kwargs("set_xticklabels", kws, **kwargs)
1381
+ st = {}
1382
+ if "color" in kwargs or "c" in kwargs:
1383
+ c = kwargs.get("color", kwargs.get("c", None))
1384
+ st["text"] = self._match_color(c)
1385
+ if "fontsize" in kwargs:
1386
+ st["font"] = self._tex_fontsize(kwargs["fontsize"])
1387
+ if st:
1388
+ self._update_axis_options("x tick label style", st)
1087
1389
  if labels:
1088
1390
  self._axis_options["xticklabels"]=f"{{{tex_text(','.join(labels))}}}"
1089
1391
  else:
@@ -1095,6 +1397,7 @@ class Axes(BaseAxes):
1095
1397
  raise Exception("Cannot create twinx() on polar plot.")
1096
1398
  self._secondary_y = Secondary(self)
1097
1399
  self._ext_xmin = self._ext_xmax = True
1400
+ self.tick_params(axis="y", right=False)
1098
1401
  return self._secondary_y
1099
1402
 
1100
1403
  def _export_imshow(self, *args, **kwargs):
@@ -1195,16 +1498,12 @@ class Axes(BaseAxes):
1195
1498
  if self._axis_options:
1196
1499
  if axis_opt_str: axis_opt_str += ",\n"
1197
1500
  if auxiliary_opt_str: auxiliary_opt_str += ",\n"
1198
- def parse_entry(k,v):
1199
- if isinstance(v, dict):
1200
- return f"{k}={{" + ",\n".join(f"{kk}={vv}" for kk, vv in v.items()) + "}"
1201
- else:
1202
- return f"{k}={v}"
1203
1501
  for k, v in self._axis_options.items():
1204
- entry = parse_entry(k,v)
1205
- axis_opt_str += entry + ",\n"
1206
- if k in ["xmin", "xmax", "ymin", "ymax", "xmode", "ymode", "log basis x", "log basis y", "width", "height", "at"]:
1207
- auxiliary_opt_str += entry + ",\n"
1502
+ if v != {}:
1503
+ entry = self._parse_entry(k,v)
1504
+ axis_opt_str += entry + ",\n"
1505
+ if k in ["xmin", "xmax", "ymin", "ymax", "xmode", "ymode", "log basis x", "log basis y", "width", "height", "at"]:
1506
+ auxiliary_opt_str += entry + ",\n"
1208
1507
  axis_opt_str = axis_opt_str.removesuffix(",,\n")
1209
1508
  auxiliary_opt_str = auxiliary_opt_str.removesuffix(",,\n")
1210
1509
  if self._colorbar:
@@ -1213,6 +1512,12 @@ class Axes(BaseAxes):
1213
1512
  axis_opt_str += f"colormap={self._cmap_bar._generate_tex_colormap(self._cmap_bar._cmap)},\n"
1214
1513
  return axis_opt_str, "hide axis,\n" + auxiliary_opt_str, alias
1215
1514
 
1515
+ def _parse_entry(self, k, v):
1516
+ if v is None:
1517
+ return f"{k}"
1518
+ if isinstance(v, dict):
1519
+ return f"{k}={{" + ",\n".join(f"{kk}={vv}" for kk, vv in v.items() if vv != {}) + "}"
1520
+ return f"{k}={v}"
1216
1521
 
1217
1522
  def _margins(self):
1218
1523
  left = TikzConfig.LEFT_PADDING + TikzConfig.YTICK_PADDING * self._yticks + TikzConfig.Y_LABEL_PADDING * ("ylabel" in self._axis_options)
@@ -1242,6 +1547,27 @@ class Axes(BaseAxes):
1242
1547
  return self._index
1243
1548
 
1244
1549
  def _to_tex(self, filename, single):
1550
+ for k,v in self._elements.items():
1551
+ if k in self._bar_labels and len(self._bar_labels[k]) > 0:
1552
+ if k in self._overlay_special and ("xbar stacked" in self._overlay_special[k] or "ybar stacked" in self._overlay_special[k]):
1553
+ self._overlay_special[k] += ",\nnodes near coords"
1554
+ for e in self._elements[k].copy():
1555
+ if e not in self._bar_labels[k]:
1556
+ self.bar_label(e, labels=[])
1557
+ for e in self._elements[k]:
1558
+ self._bar_labels[k][e]["stacked"] = True
1559
+ for e in self._bar_labels[k]:
1560
+ e._add_bar_labels(**self._bar_labels[k][e])
1561
+ """if isinstance(v, list) and len(v) > 1:
1562
+ reference = getattr(v[0], "_settings", {}) or {}
1563
+ common_style = {s: val for s, val in reference.items() if all((getattr(obj, "_settings", {}) or {}).get(s) == val for obj in v[1:])}
1564
+ forbiden = {"draw", "fill", "xbar", "ybar"}
1565
+ for f in forbiden:
1566
+ common_style.pop(f, None)
1567
+ for key, val in common_style.items():
1568
+ for e in v:
1569
+ e._settings.pop(key, None)
1570
+ self._overlay_special[k] += f",\n{self._parse_entry(key, val)}" """
1245
1571
  lines = []
1246
1572
  lines2 = []
1247
1573
  if self._polar:
@@ -1341,14 +1667,14 @@ class Secondary(BaseAxes):
1341
1667
  self._axis_options["axis x line"] = "none"
1342
1668
  self._axis_options["at"] = f"{{({primary._axis_options['alias' if 'alias' in primary._axis_options else 'name']}.south west)}}"
1343
1669
  self._axis_options["anchor"] = "south west"
1344
- self._axis_options["y label style"] = r"{at={(" + str(TikzConfig.SEC_YLABEL_LOC[0]) + "," + str(TikzConfig.SEC_YLABEL_LOC[1]) + ")}, rotate=180}"
1670
+ self._update_axis_options("y label style", {"at": f"{{({TikzConfig.SEC_YLABEL_LOC[0]},{TikzConfig.SEC_YLABEL_LOC[1]})}}", "rotate": 180})
1345
1671
 
1346
1672
  self._fig = primary._fig
1347
1673
  self._style = self._fig._style
1348
1674
  self._style_defaults()
1349
1675
 
1350
1676
  def _style_defaults(self):
1351
- _add_settgs = self._style._get_additional_settings()
1677
+ _add_settgs = copy.deepcopy(self._style._get_additional_settings())
1352
1678
  if _add_settgs is not None:
1353
1679
  self._axis_options = _add_settgs | self._axis_options
1354
1680