tikzplot42 0.3.9__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 (54) hide show
  1. {tikzplot42-0.3.9/src/tikzplot42.egg-info → tikzplot42-0.4.5}/PKG-INFO +4 -2
  2. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/README.md +3 -1
  3. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/pyproject.toml +1 -1
  4. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/axes.py +272 -61
  5. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/axes.pyi +69 -23
  6. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/axes3d.py +26 -8
  7. tikzplot42-0.4.5/src/tikzplot/axes3d.pyi +43 -0
  8. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/colorbar.py +14 -2
  9. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/colorbar.pyi +13 -13
  10. tikzplot42-0.4.5/src/tikzplot/colors.py +228 -0
  11. tikzplot42-0.4.5/src/tikzplot/config.py +156 -0
  12. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/config.pyi +63 -4
  13. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/elements.py +89 -58
  14. tikzplot42-0.4.5/src/tikzplot/elements.pyi +92 -0
  15. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/figure.py +23 -6
  16. tikzplot42-0.4.5/src/tikzplot/figure.pyi +82 -0
  17. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/plots.py +92 -9
  18. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/plots.pyi +53 -57
  19. tikzplot42-0.4.5/src/tikzplot/py.typed +2 -0
  20. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/state.py +6 -5
  21. tikzplot42-0.4.5/src/tikzplot/styles.py +94 -0
  22. tikzplot42-0.4.5/src/tikzplot/styles.pyi +41 -0
  23. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/texts.py +2 -2
  24. {tikzplot42-0.3.9 → tikzplot42-0.4.5/src/tikzplot42.egg-info}/PKG-INFO +4 -2
  25. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/SOURCES.txt +8 -3
  26. tikzplot42-0.4.5/tests/test11.py +100 -0
  27. tikzplot42-0.4.5/tests/test12.py +32 -0
  28. tikzplot42-0.4.5/tests/test13.py +28 -0
  29. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test3.py +1 -1
  30. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test8.py +1 -3
  31. tikzplot42-0.4.5/tests/test_styles.py +17 -0
  32. tikzplot42-0.3.9/src/tikzplot/colors.py +0 -58
  33. tikzplot42-0.3.9/src/tikzplot/colors.pyi +0 -1
  34. tikzplot42-0.3.9/src/tikzplot/config.py +0 -103
  35. tikzplot42-0.3.9/src/tikzplot/elements.pyi +0 -11
  36. tikzplot42-0.3.9/src/tikzplot/figure.pyi +0 -34
  37. tikzplot42-0.3.9/src/tikzplot/py.typed +0 -0
  38. tikzplot42-0.3.9/src/tikzplot/state.pyi +0 -3
  39. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/LICENSE +0 -0
  40. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/setup.cfg +0 -0
  41. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/__init__.py +0 -0
  42. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/__init__.pyi +0 -0
  43. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot/latex_special.py +0 -0
  44. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/dependency_links.txt +0 -0
  45. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/requires.txt +0 -0
  46. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/src/tikzplot42.egg-info/top_level.txt +0 -0
  47. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test1.py +0 -0
  48. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test10.py +0 -0
  49. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test2.py +0 -0
  50. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test4.py +0 -0
  51. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test5.py +0 -0
  52. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test6.py +0 -0
  53. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test7.py +0 -0
  54. {tikzplot42-0.3.9 → tikzplot42-0.4.5}/tests/test9.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tikzplot42
3
- Version: 0.3.9
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
@@ -765,6 +765,8 @@ If axes and plot have different name for command with same effect, both are impl
765
765
  - `ax.twinx()`,
766
766
  - `ax.set()`.
767
767
 
768
+ 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
+
768
770
  #### Supported common **kwargs
769
771
  - `color/c` (all matplotlib formats, except color schemes which are not yet implemented),
770
772
  - `linestyle/ls`,
@@ -788,6 +790,6 @@ For plot configurations (default sizes, paddings, etc.), use `from tikzplot impo
788
790
  Currently supported parameters are listed with their default values in `src/tikzplot/config.py`.
789
791
 
790
792
  ## More features coming soon...
791
- - basic color schemes,
793
+ - more developed styles,
792
794
  - improved 3D plots (currently only basic support: plot, errorbar, fill_between, scatter, plot_wireframe, plot_surface),
793
795
  - ...
@@ -63,6 +63,8 @@ If axes and plot have different name for command with same effect, both are impl
63
63
  - `ax.twinx()`,
64
64
  - `ax.set()`.
65
65
 
66
+ 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).
67
+
66
68
  #### Supported common **kwargs
67
69
  - `color/c` (all matplotlib formats, except color schemes which are not yet implemented),
68
70
  - `linestyle/ls`,
@@ -86,6 +88,6 @@ For plot configurations (default sizes, paddings, etc.), use `from tikzplot impo
86
88
  Currently supported parameters are listed with their default values in `src/tikzplot/config.py`.
87
89
 
88
90
  ## More features coming soon...
89
- - basic color schemes,
91
+ - more developed styles,
90
92
  - improved 3D plots (currently only basic support: plot, errorbar, fill_between, scatter, plot_wireframe, plot_surface),
91
93
  - ...
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tikzplot42"
7
- version = "0.3.9"
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):
@@ -21,18 +25,21 @@ class BaseAxes:
21
25
  else:
22
26
  self._axis_args.add(f"/pgf/number format/.cd, 1000 sep={{{TikzConfig.THOUSANDS_SEP}}}")
23
27
 
24
- self._add_legend = ""
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=None, xerr=None, yerr=None, **style):
32
- if not isinstance(self, Secondary) and self._polar:
36
+ self._reverse_elements = []
37
+
38
+ def _plot(self, x, y, settings={}, xerr=None, yerr=None, **style):
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)
@@ -71,6 +78,7 @@ class BaseAxes:
71
78
 
72
79
  try:
73
80
  c = kwargs.get("c", kwargs.get("color", None))
81
+ if c is None: raise ValueError("No color specified")
74
82
  if len(c) == len(x):
75
83
  if isinstance(c[0], (int, float)):
76
84
  if "cmap" not in kwargs:
@@ -87,7 +95,7 @@ class BaseAxes:
87
95
  self._cmap_bar = kwargs["cmap"]
88
96
  except: pass
89
97
 
90
- return self._plot(x, y, **kwargs, ls="", settings=["scatter"])
98
+ return self._plot(x, y, **kwargs, ls="", settings={"scatter": None})
91
99
 
92
100
  def semilogy(self, x, y, *args, **kwargs):
93
101
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "marker", "markersize", "ms", "label"}
@@ -118,17 +126,16 @@ class BaseAxes:
118
126
  x = range(len(y))
119
127
  elif len(args) == 2:
120
128
  x,y=args
121
- elif len(args) == 3:
122
- x,y,fmt=args
123
- kwargs["fmt"]=fmt
129
+ else:
130
+ raise Warning("Invalid number of args for stem.")
124
131
  if "orientation" in kwargs:
125
132
  o = kwargs.pop("orientation")
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"}
@@ -139,6 +146,7 @@ class BaseAxes:
139
146
  return el._try_set_pname(pname)
140
147
  else:
141
148
  return None
149
+ assert self._fig is not None
142
150
  name1 = self._fig._get_free_path_name()
143
151
  name2 = self._fig._get_free_path_name()
144
152
  if isinstance(y1, (int, float)):
@@ -165,7 +173,7 @@ class BaseAxes:
165
173
  self._plot(xs,ys,path_name=name2, alpha=0)
166
174
  else:
167
175
  name2 = inst
168
- e = Graph(self, f"fill between [of={name1} and {name2}]",settings=None, xerr=None, yerr=None, **kwargs)
176
+ e = Graph(self, f"fill between [of={name1} and {name2}]",settings={}, xerr=None, yerr=None, **kwargs)
169
177
  self._elements.append(e)
170
178
  return e
171
179
 
@@ -214,46 +222,150 @@ class BaseAxes:
214
222
  self._plot([xs[i]]*2, [ymins[i], ymaxs[i]], None, None, None, c=colorss[i], ls=lss[i])
215
223
 
216
224
  def hist(self, x, bins=10, density=False,**kwargs):
217
- #kws = {"alpha", "color", "c", "label"}
218
- #kwargs = self._check_kwargs("hist", kws, **kwargs)
219
- try:
220
- iter(x)
221
- iter(x[0])
222
- datasets = x
223
- except:
224
- 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]
225
261
  all_data = _np.concatenate(datasets)
226
- edges = _np.histogram_bin_edges(all_data, bins=bins)
227
- for data in datasets:
228
- counts, _ = _np.histogram(data, edges, density=density)
229
- centers = (edges[:-1] + edges[1:]) / 2
262
+ edges = _np.histogram_bin_edges(all_data, bins=bins, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
230
263
  widths = edges[1:] - edges[:-1]
231
- 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}.")
232
269
  if "orientation" in kwargs and kwargs["orientation"] == "horizontal":
233
- settings.append("xbar")
270
+ settings["xbar"] = None
234
271
  else:
235
- settings.append("ybar")
236
- settings.append("fill")
237
- if "rwidth" in kwargs:
238
- 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)
239
283
  else:
240
- 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
241
290
  if "range" in kwargs:
242
- if settings[0] == "xbar":
291
+ if "xbar" in settings:
243
292
  self.set_ylim(kwargs["range"])
244
- else:
293
+ elif isinstance(self, Axes):
245
294
  self.set_xlim(kwargs["range"])
246
- if "cumulative" in kwargs and kwargs["cumulative"]:
247
- counts = _np.cumsum(counts)
248
-
249
- return self._plot(centers, counts, settings=settings, **kwargs)
295
+ elif isinstance(self, Secondary):
296
+ self._primary.set_xlim(kwargs["range"])
297
+ base_settings = settings.copy()
298
+ outputs = []
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)}"
342
+ if "cumulative" in kwargs and kwargs["cumulative"]:
343
+ counts = _np.cumsum(counts)
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])
361
+ return outputs
250
362
 
251
363
  def step(self, x, y, *args, **kwargs):
252
364
  kws = {"fmt", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "marker", "markersize", "ms", "label", "where"}
253
365
  kwargs = self._check_kwargs("step", kws, **kwargs)
254
366
  WHERE_DICT = {"pre": "left", "post": "right", "mid": "mid"}
255
367
  where = WHERE_DICT.get(kwargs.pop("where", "pre"), None)
256
- settings = [f"const plot mark {where}"]
368
+ settings = {f"const plot mark {where}": None}
257
369
  if len(args) == 1:
258
370
  kwargs["fmt"] = args[0]
259
371
  return self._plot(x,y,settings=settings, **kwargs)
@@ -262,7 +374,7 @@ class BaseAxes:
262
374
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
263
375
  kwargs = self._check_kwargs("axvline", kws, **kwargs)
264
376
  self._ext_ymin = self._ext_ymax = True
265
- self._plot(x, (ymin, ymax), settings="axvline", **kwargs)
377
+ self._plot(x, (ymin, ymax), settings={"axvline": None}, **kwargs)
266
378
 
267
379
  def axhline(self, y, xmin=0, xmax=1, **kwargs):
268
380
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
@@ -271,7 +383,7 @@ class BaseAxes:
271
383
  self._primary._ext_xmin = self._primary._ext_xmax = True
272
384
  else:
273
385
  self._ext_xmin = self._ext_xmax = True
274
- self._plot((xmin, xmax), y, settings="axhline", **kwargs)
386
+ self._plot((xmin, xmax), y, settings={"axhline": None}, **kwargs)
275
387
 
276
388
  def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
277
389
  kws = {"c", "color", "alpha", "label"}
@@ -279,7 +391,7 @@ class BaseAxes:
279
391
  self._ext_ymin = self._ext_ymax = True
280
392
  if TikzConfig.USE_GROUPPLOTS:
281
393
  self._axis_args.add("set layers")
282
- self._plot([xmin, xmax], [ymin, ymax], settings="axvspan", **kwargs)
394
+ self._plot([xmin, xmax], [ymin, ymax], settings={"axvspan": None}, **kwargs)
283
395
 
284
396
  def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
285
397
  kws = {"c", "color", "alpha", "label"}
@@ -290,7 +402,7 @@ class BaseAxes:
290
402
  self._ext_xmin = self._ext_xmax = True
291
403
  if TikzConfig.USE_GROUPPLOTS:
292
404
  self._axis_args.add("set layers")
293
- self._plot([xmin, xmax], [ymin, ymax], settings="axhspan", **kwargs)
405
+ self._plot([xmin, xmax], [ymin, ymax], settings={"axhspan": None}, **kwargs)
294
406
 
295
407
  def set_ylabel(self, label):
296
408
  self._axis_options["ylabel"] = f"{{{tex_text(label)}}}"
@@ -350,7 +462,19 @@ class BaseAxes:
350
462
  _LEGEND_LOC_MAP = ["best", "upper right", "upper left", "lower_left", "lower right", "right", "center left", "center right", "lower center", "upper center", "center"]
351
463
  _ANCHOR_MAP = {"top": "north", "bottom": "south", "upper": "north", "lower": "south", "left": "west", "right": "east", "center": "center"}
352
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
+
353
476
  def legend(self, *args, **kwargs):
477
+ legend_string = {}
354
478
  if "loc" in kwargs:
355
479
  loc = kwargs["loc"]
356
480
  lx = ly = posit = None
@@ -358,7 +482,8 @@ class BaseAxes:
358
482
  try:
359
483
  lx,ly=float(loc[0]), float(loc[1])
360
484
  posit = "south west"
361
- except: print(f"Error parsing legend location: {loc}")
485
+ except:
486
+ print(f"Error parsing legend location: {loc}")
362
487
  else:
363
488
  if isinstance(loc, int):
364
489
  loc = self._LEGEND_LOC_MAP[loc]
@@ -376,17 +501,34 @@ class BaseAxes:
376
501
  elif "east" in posit:
377
502
  lx = 1 - TikzConfig.LEGEND_REL_X
378
503
 
379
- legend_string = []
380
504
  if lx is not None and ly is not None:
381
- legend_string.append(r"at={(" + f"{lx},{ly}" + r")}")
382
- if len(posit):
383
- legend_string.append(r"anchor=" + posit)
384
- self._axis_options["legend style"] = f"{{{','.join(legend_string)}}}"
505
+ legend_string["at"] = "{(" + f"{lx},{ly}" + r")}"
506
+ if posit is not None and len(posit):
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
385
527
  self._legend_on = True
386
528
  if "ncols" in kwargs:
387
529
  self._axis_options["legend columns"] = kwargs["ncols"]
388
530
  if len(args) == 2:
389
- self._add_legend = args
531
+ self._add_legend = list(args)
390
532
  elif len(args) == 1:
391
533
  labs = args[0]
392
534
  if len(labs) > len(self._elements):
@@ -395,10 +537,12 @@ class BaseAxes:
395
537
  for i in range(len(labs)):
396
538
  self._elements[i]._set_label(tex_text(labs[i]))
397
539
 
398
- def text(self, x, y, s, on_top=True, **kwargs):
399
- kws = {"alpha", "color", "c", "fontsize", "size", "backgroundcolor", "horizontalalignment", "ha", "verticalalignment", "va", "rotation", "label"}
540
+ def text(self, x, y, s, **kwargs):
541
+ kws = {"alpha", "color", "c", "fontsize", "on_top", "size", "backgroundcolor", "horizontalalignment", "ha", "verticalalignment", "va", "rotation", "label"}
400
542
  kwargs = self._check_kwargs("text", kws, **kwargs)
543
+ on_top = kwargs.pop("on_top", True)
401
544
  if on_top:
545
+ assert self._fig is not None
402
546
  coord = self._fig._next_coordinate_name()
403
547
  txt = Text(self, x, y, coord, s, **kwargs)
404
548
  self._fig._add_text(txt)
@@ -409,12 +553,13 @@ class BaseAxes:
409
553
  def magnify(self, x_p, y_p, x_m, y_m, zoom, size, **kwargs):
410
554
  kws = {"shape", "connect"}
411
555
  kwargs = self._check_kwargs("magnify", kws, **kwargs)
556
+ assert self._fig is not None
412
557
  n = self._fig._add_spy(zoom, size, **kwargs)
413
558
  self._coordinates.update({f"spypoint{n}": (x_p,y_p)})
414
559
  self._coordinates.update({f"spyviewr{n}": (x_m,y_m)})
415
560
 
416
561
  def _add_legend_entries(self):
417
- if self._add_legend == "": return ""
562
+ if self._add_legend == []: return ""
418
563
  axs, labs = self._add_legend
419
564
  output = ""
420
565
  if len(axs) != len(labs):
@@ -422,16 +567,36 @@ class BaseAxes:
422
567
  return ""
423
568
  for i in range(len(axs)):
424
569
  output += f"\n\\addlegendimage{{{axs[i]._style_string()}}}"
425
- 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])}}}"
426
574
  return output
427
575
 
428
576
  def _content_tex(self, filename):
429
- ouptut = "\n".join(e._to_tex(filename) for e in self._elements)
430
- 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()
431
596
  for coord in self._coordinates:
432
597
  x,y = self._coordinates[coord]
433
- ouptut += f"\n\\coordinate ({coord}) at ({x},{y});"
434
- return ouptut
598
+ output += f"\n\\coordinate ({coord}) at ({x},{y});"
599
+ return output
435
600
 
436
601
  def _get_hard_range(self,which):
437
602
  arg = f"{which[0]}mode"
@@ -499,6 +664,7 @@ class BaseAxes:
499
664
  e._reduce_points(limit)
500
665
 
501
666
  def _add_col(self, r,g,b):
667
+ assert self._fig is not None
502
668
  self._fig._add_col(r,g,b)
503
669
 
504
670
  def set(self, **kwargs):
@@ -507,6 +673,10 @@ class BaseAxes:
507
673
  if attr in kwargs:
508
674
  defined[attr](kwargs.pop(attr))
509
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
+
510
680
  class Axes(BaseAxes):
511
681
 
512
682
  def __init__(self, nrows, ncols, index, fig, polar):
@@ -521,6 +691,7 @@ class Axes(BaseAxes):
521
691
  self._col = self._index - self._row * self._ncols
522
692
 
523
693
  self._fig = fig
694
+ self._style = self._fig._style
524
695
  self._imshow = None
525
696
 
526
697
  self._defcol_counter = 0
@@ -559,6 +730,18 @@ class Axes(BaseAxes):
559
730
 
560
731
  self._xticks = True
561
732
 
733
+ self._style_defaults()
734
+
735
+ def _style_defaults(self):
736
+ _gs = self._style._get_grid_cycle()
737
+ if _gs is not None:
738
+ self.grid(**_gs)
739
+ _bcgnd = self._style._get_background_cycle()
740
+ if _bcgnd is not None:
741
+ self._axis_options["axis background/.style"] = f"{{{_bcgnd}}}"
742
+ _add_settgs = self._style._get_additional_settings()
743
+ if _add_settgs is not None:
744
+ self._axis_options = _add_settgs | self._axis_options
562
745
 
563
746
  def _update_size(self):
564
747
  if self._fig._get_width():
@@ -629,7 +812,7 @@ class Axes(BaseAxes):
629
812
  if kwargs:
630
813
  accepted_kwargs = {"color", "c", "linestyle", "ls", "linewidth", "lw", "alpha"}
631
814
  kwargs = self._check_kwargs("grid", accepted_kwargs, **kwargs)
632
- g = Graph(self, None, None, None, None, **kwargs)._style_string()
815
+ g = Graph(self, None, {}, None, None, **kwargs)._style_string()
633
816
  self._axis_options[f"{selector}grid style"] = f"{{{g}}}"
634
817
 
635
818
  def set_minorticks_num(self, num):
@@ -708,7 +891,7 @@ class Axes(BaseAxes):
708
891
  _plt.axis("off")
709
892
  _plt.imshow(*args, **kwargs)
710
893
  im_name = f"{str(main_name()[1]).removesuffix('.py')}_{TikzConfig.IMSHOW_SAVENAME}{_next_imshow_num()}.pdf"
711
- _plt.savefig(im_name,bbox_inches='tight', pad_inches=0)
894
+ _plt.savefig(im_name, bbox_inches='tight', pad_inches=0)
712
895
  return im_name
713
896
 
714
897
  def _axis_option_string(self):
@@ -729,7 +912,7 @@ class Axes(BaseAxes):
729
912
  if "extent" in self._imshow[1]:
730
913
  bounds = self._imshow[1]["extent"]
731
914
  xm, xM, ym, yM = bounds
732
- self._elements.insert(0, Graph(self, f"graphics [xmin={xm}, xmax={xM}, ymin={ym}, ymax={yM}] {{{im_name}}}",settings=None, 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"))
733
916
  axis_opt_str = ""
734
917
  if self._axis_args:
735
918
  axis_opt_str += ",\n".join(self._axis_args)
@@ -762,7 +945,13 @@ class Axes(BaseAxes):
762
945
  self._axis_options["ymax"] = self._fig._next_limname("ymax", self._axis_options.get("ymax", yM))
763
946
  if self._axis_options:
764
947
  if axis_opt_str: axis_opt_str += ",\n"
765
- 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"
766
955
  if self._colorbar:
767
956
  axis_opt_str += self._colorbar
768
957
  elif self._cmap_bar:
@@ -834,6 +1023,15 @@ class Axes(BaseAxes):
834
1023
  defined[attr](kwargs.pop(attr))
835
1024
 
836
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(".", "")
837
1035
 
838
1036
  class Secondary(BaseAxes):
839
1037
  def __init__(self, primary):
@@ -847,6 +1045,13 @@ class Secondary(BaseAxes):
847
1045
  self._axis_options["y label style"] = r"{at={(" + str(TikzConfig.SEC_YLABEL_LOC[0]) + "," + str(TikzConfig.SEC_YLABEL_LOC[1]) + ")}, rotate=180}"
848
1046
 
849
1047
  self._fig = primary._fig
1048
+ self._style = self._fig._style
1049
+ self._style_defaults()
1050
+
1051
+ def _style_defaults(self):
1052
+ _add_settgs = self._style._get_additional_settings()
1053
+ if _add_settgs is not None:
1054
+ self._axis_options = _add_settgs | self._axis_options
850
1055
 
851
1056
  def _axis_option_string(self):
852
1057
  if self._primary._width:
@@ -868,7 +1073,13 @@ class Secondary(BaseAxes):
868
1073
  self._axis_options["xmax"] = self._primary._axis_options["xmax"]
869
1074
  if self._axis_options:
870
1075
  if axis_opt_str: axis_opt_str += ",\n"
871
- 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"
872
1083
  return axis_opt_str
873
1084
 
874
1085
  def _padding(self):