tikzplot42 0.4.3__tar.gz → 0.4.6__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.4.3/src/tikzplot42.egg-info → tikzplot42-0.4.6}/PKG-INFO +1 -1
  2. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/pyproject.toml +1 -1
  3. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/axes.py +245 -53
  4. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/axes.pyi +127 -13
  5. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/axes3d.py +4 -4
  6. tikzplot42-0.4.6/src/tikzplot/colors.py +228 -0
  7. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/elements.py +115 -51
  8. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/elements.pyi +3 -3
  9. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/figure.py +14 -2
  10. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/figure.pyi +7 -1
  11. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/plots.py +14 -2
  12. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/plots.pyi +122 -16
  13. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/texts.py +2 -2
  14. {tikzplot42-0.4.3 → tikzplot42-0.4.6/src/tikzplot42.egg-info}/PKG-INFO +1 -1
  15. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot42.egg-info/SOURCES.txt +2 -0
  16. tikzplot42-0.4.6/tests/test12.py +32 -0
  17. tikzplot42-0.4.6/tests/test13.py +29 -0
  18. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test2.py +2 -1
  19. tikzplot42-0.4.6/tests/test4.py +9 -0
  20. tikzplot42-0.4.3/src/tikzplot/colors.py +0 -60
  21. tikzplot42-0.4.3/tests/test4.py +0 -8
  22. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/LICENSE +0 -0
  23. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/README.md +0 -0
  24. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/setup.cfg +0 -0
  25. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/__init__.py +0 -0
  26. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/__init__.pyi +0 -0
  27. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/axes3d.pyi +0 -0
  28. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/colorbar.py +0 -0
  29. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/colorbar.pyi +0 -0
  30. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/config.py +0 -0
  31. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/config.pyi +0 -0
  32. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/latex_special.py +0 -0
  33. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/py.typed +0 -0
  34. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/state.py +0 -0
  35. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/styles.py +0 -0
  36. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot/styles.pyi +0 -0
  37. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot42.egg-info/dependency_links.txt +0 -0
  38. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot42.egg-info/requires.txt +0 -0
  39. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/src/tikzplot42.egg-info/top_level.txt +0 -0
  40. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test1.py +0 -0
  41. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test10.py +0 -0
  42. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test11.py +0 -0
  43. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test3.py +0 -0
  44. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test5.py +0 -0
  45. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test6.py +0 -0
  46. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test7.py +0 -0
  47. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test8.py +0 -0
  48. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test9.py +0 -0
  49. {tikzplot42-0.4.3 → tikzplot42-0.4.6}/tests/test_styles.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tikzplot42
3
- Version: 0.4.3
3
+ Version: 0.4.6
4
4
  Summary: TikzPlot to make TikZ/PGFPlots plots with matplotlib.pyplot-like syntax.
5
5
  Author-email: Zan Ambrozic <zan.ambrozic7@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tikzplot42"
7
- version = "0.4.3"
7
+ version = "0.4.6"
8
8
  description = "TikzPlot to make TikZ/PGFPlots plots with matplotlib.pyplot-like syntax."
9
9
  authors = [
10
10
  { name = "Zan Ambrozic", email = "zan.ambrozic7@gmail.com" }
@@ -1,3 +1,6 @@
1
+ from typing import Any
2
+ from xml.dom import XMLNS_NAMESPACE
3
+
1
4
  import numpy as _np
2
5
  import matplotlib.pyplot as _plt
3
6
 
@@ -7,6 +10,7 @@ from .config import TikzConfig
7
10
  from .colorbar import Colorbar
8
11
  from .state import _next_imshow_num, main_name
9
12
  from .latex_special import tex_text
13
+ from .colors import _tex_color
10
14
 
11
15
  class BaseAxes:
12
16
  def __init__(self):
@@ -22,17 +26,20 @@ class BaseAxes:
22
26
  self._axis_args.add(f"/pgf/number format/.cd, 1000 sep={{{TikzConfig.THOUSANDS_SEP}}}")
23
27
 
24
28
  self._add_legend = []
29
+ self._legend_lab_col: Any = None
25
30
  self._coordinates = {}
26
31
  self._cmap_bar = None
27
32
 
28
33
  self._ext_ymin = False
29
34
  self._ext_ymax = False
30
35
 
31
- def _plot(self, x, y, settings=[], xerr=None, yerr=None, **style):
36
+ self._reverse_elements = []
37
+
38
+ def _plot(self, x, y, settings={}, xerr=None, yerr=None, **style):
32
39
  if isinstance(self, Axes) and self._polar:
33
40
  x = _np.rad2deg(x)
34
41
  e = Graph(self, (x, y), settings, xerr=xerr, yerr=yerr, **style)
35
- if TikzConfig.USE_GROUPPLOTS and ("axvspan" == settings or "axhspan" == settings):
42
+ if TikzConfig.USE_GROUPPLOTS and ("axvspan" in settings or "axhspan" in settings):
36
43
  self._elements.insert(0, e)
37
44
  else:
38
45
  self._elements.append(e)
@@ -88,7 +95,7 @@ class BaseAxes:
88
95
  self._cmap_bar = kwargs["cmap"]
89
96
  except: pass
90
97
 
91
- return self._plot(x, y, **kwargs, ls="", settings=["scatter"])
98
+ return self._plot(x, y, **kwargs, ls="", settings={"scatter": None})
92
99
 
93
100
  def semilogy(self, x, y, *args, **kwargs):
94
101
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "marker", "markersize", "ms", "label"}
@@ -126,12 +133,12 @@ class BaseAxes:
126
133
  if o == "horizontal":
127
134
  vert = False
128
135
  if vert:
129
- return self._plot(x,y,settings=["ycomb"], **kwargs)
136
+ return self._plot(x,y,settings={"ycomb": None}, **kwargs)
130
137
  else:
131
- return self._plot(y,x,settings=["xcomb"], **kwargs)
138
+ return self._plot(y,x,settings={"xcomb": None}, **kwargs)
132
139
 
133
140
  def fill_between(self, x, y1, y2=None, **kwargs):
134
- kws = {"fmt", "alpha", "color", "c", "label"}
141
+ kws = {"fmt", "alpha", "color", "c", "label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
135
142
  kwargs = self._check_kwargs("fill_between", kws, **kwargs)
136
143
  def _check_instance(xs, ys, pname):
137
144
  for el in self._elements:
@@ -166,7 +173,7 @@ class BaseAxes:
166
173
  self._plot(xs,ys,path_name=name2, alpha=0)
167
174
  else:
168
175
  name2 = inst
169
- e = Graph(self, f"fill between [of={name1} and {name2}]",settings=[], xerr=None, yerr=None, **kwargs)
176
+ e = Graph(self, f"fill between [of={name1} and {name2}]",settings={}, xerr=None, yerr=None, **kwargs)
170
177
  self._elements.append(e)
171
178
  return e
172
179
 
@@ -215,41 +222,155 @@ class BaseAxes:
215
222
  self._plot([xs[i]]*2, [ymins[i], ymaxs[i]], None, None, None, c=colorss[i], ls=lss[i])
216
223
 
217
224
  def hist(self, x, bins=10, density=False,**kwargs):
218
- #kws = {"alpha", "color", "c", "label"}
219
- #kwargs = self._check_kwargs("hist", kws, **kwargs)
220
- try:
221
- iter(x)
222
- iter(x[0])
223
- datasets = x
224
- except:
225
- datasets = [x]
225
+ kws = {"alpha", "color", "c", "label", "facecolor", "fc", "edgecolor", "ec", "orientation", "rwidth", "cumulative", "range", "histtype", "weights", "cumulative", "align", "stacked", "fill", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
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", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"]:
250
+ if kw in kwargs:
251
+ if isinstance(kwargs[kw], (list)):
252
+ if len(kwargs[kw]) != len(datasets):
253
+ raise Warning(f"Length of {kw} does not match number of datasets.")
254
+ prop = kwargs.pop(kw)
255
+ else:
256
+ prop = [kwargs.pop(kw)] * len(datasets)
257
+ for i in range(len(datasets)):
258
+ if i not in datas:
259
+ datas[i] = {}
260
+ datas[i][kw] = prop[i]
226
261
  all_data = _np.concatenate(datasets)
227
- edges = _np.histogram_bin_edges(all_data, bins=bins)
262
+ edges = _np.histogram_bin_edges(all_data, bins=bins, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
228
263
  widths = edges[1:] - edges[:-1]
229
- settings = []
264
+ offset = 0
265
+ settings = {}
266
+ hist_type = kwargs.pop("histtype", "bar")
267
+ if hist_type not in ["bar", "barstacked", "step", "stepfilled"]:
268
+ raise Warning(f"Invalid histtype: {hist_type}.")
230
269
  if "orientation" in kwargs and kwargs["orientation"] == "horizontal":
231
- settings.append("xbar")
270
+ settings["xbar"] = None
232
271
  else:
233
- settings.append("ybar")
234
- settings.append("fill")
235
- if "rwidth" in kwargs:
236
- settings.append(f"bar width={widths.mean()*kwargs['rwidth']}")
272
+ settings["ybar"] = None
273
+ if ("rwidth" in kwargs or (len(datasets) > 1 and not stack)) and isinstance(bins, int):
274
+ if stack or len(datasets) == 1:
275
+ settings["bar width"] = f"{widths.mean()*kwargs['rwidth']}"
276
+ else:
277
+ if "rwidth" in kwargs:
278
+ settings["bar width"] = f"{widths.mean()*kwargs['rwidth']/(len(datasets)+1)}"
279
+ offset = widths.mean() * kwargs['rwidth'] / (len(datasets) + 1)
280
+ else:
281
+ settings["bar width"] = f"{widths.mean()/(len(datasets)+1)}"
282
+ offset = widths.mean() / (len(datasets) + 1)
237
283
  else:
238
- settings[0] += " interval"
284
+ if "xbar" in settings:
285
+ settings.pop("xbar")
286
+ settings["xbar interval"] = None
287
+ elif "ybar" in settings:
288
+ settings.pop("ybar")
289
+ settings["ybar interval"] = None
239
290
  if "range" in kwargs:
240
- if settings[0] == "xbar":
291
+ if "xbar" in settings:
241
292
  self.set_ylim(kwargs["range"])
242
293
  elif isinstance(self, Axes):
243
294
  self.set_xlim(kwargs["range"])
244
295
  elif isinstance(self, Secondary):
245
296
  self._primary.set_xlim(kwargs["range"])
297
+ base_settings = settings.copy()
246
298
  outputs = []
247
- for data in datasets:
248
- counts, _ = _np.histogram(data, edges, density=density)
249
- centers = (edges[:-1] + edges[1:]) / 2
299
+ old_counts = _np.zeros(len(edges), dtype=_np.float64)
300
+ totals, _ = _np.histogram(all_data, edges, density=False, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
301
+ tot_sum = totals.sum()
302
+ align_offset = 0
303
+ if "align" in kwargs:
304
+ if kwargs["align"] not in ["left", "mid", "right"]:
305
+ raise Warning(f"Invalid align value: {kwargs['align']}. Must be 'left', 'mid', or 'right'.")
306
+ if kwargs["align"] == "left":
307
+ align_offset = -widths.mean()/2
308
+ elif kwargs["align"] == "mid":
309
+ align_offset = 0
310
+ elif kwargs["align"] == "right":
311
+ align_offset = widths.mean()/2
312
+ for i in range(len(datasets)):
313
+ data = datasets[i]
314
+ settings = base_settings.copy()
315
+ kws = datas.get(i, {})
316
+ if "label" in kws:
317
+ if "xbar" in settings:
318
+ settings["xbar legend"] = None
319
+ elif "xbar interval" in settings:
320
+ settings["xbar interval legend"] = None
321
+ elif "ybar" in settings:
322
+ settings["ybar legend"] = None
323
+ else:
324
+ settings["ybar interval legend"] = None
325
+ fill = kws.pop("facecolor", kws.pop("fc", kws.pop("color", kws.pop("c", None))))
326
+ draw = kws.pop("edgecolor", kws.pop("ec", None))
327
+ if fill:
328
+ fill = self._match_color(fill)
329
+ settings["fill"] = fill
330
+ if draw and hist_type != "stepfilled":
331
+ draw = self._match_color(draw)
332
+ settings["draw"] = draw
333
+ else:
334
+ settings["draw"] = "none"
335
+ counts, _ = _np.histogram(data, edges, density=density, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
336
+ if "xbar interval" in settings or "ybar interval" in settings: # edges for interval
337
+ if "rwidth" in kwargs:
338
+ xs = [edges[0] + widths[0]*(1-kwargs["rwidth"])/2]
339
+ new_counts = []
340
+ for j in range(len(widths)):
341
+ xs.append(xs[-1] + widths[j]*kwargs["rwidth"])
342
+ new_counts.append(counts[j])
343
+ if j < len(counts)-1:
344
+ xs.append(xs[-1] + (widths[j] + widths[j+1])*(1-kwargs["rwidth"])/2)
345
+ new_counts.append(0)
346
+ counts = new_counts + [0]
347
+ else:
348
+ xs = edges
349
+ counts = _np.concatenate([counts, [0]])
350
+ else: # centers
351
+ xs = (edges[:-1] + edges[1:]) / 2
352
+ if offset > 0:
353
+ settings["bar shift"] = f"{offset*(i - len(datasets)/2 + 0.5)}"
354
+ xs = xs + align_offset * _np.ones_like(xs)
250
355
  if "cumulative" in kwargs and kwargs["cumulative"]:
251
356
  counts = _np.cumsum(counts)
252
- outputs.append(self._plot(centers, counts, settings=settings, **kwargs))
357
+ if stack:
358
+ if density:
359
+ set_counts, _ = _np.histogram(data, edges, density=False, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
360
+ counts = counts * _np.sum(set_counts) / tot_sum
361
+ counts = _np.asarray(counts, dtype=_np.float64)
362
+ counts += old_counts
363
+ old_counts = counts.copy()
364
+ if hist_type == "step":
365
+ kwargs.pop("facecolor", None)
366
+ kwargs.pop("fc", None)
367
+ kwargs.pop("fill", None)
368
+ self.step([edges[0]] + list(edges), [0] + list(counts), where="pre", **kws)
369
+ else:
370
+ e = self._plot(xs, counts, settings=settings, **kws)
371
+ outputs.append(e)
372
+ if outputs and stack:
373
+ self._reverse_elements.append([id(e) for e in outputs])
253
374
  return outputs
254
375
 
255
376
  def step(self, x, y, *args, **kwargs):
@@ -257,7 +378,7 @@ class BaseAxes:
257
378
  kwargs = self._check_kwargs("step", kws, **kwargs)
258
379
  WHERE_DICT = {"pre": "left", "post": "right", "mid": "mid"}
259
380
  where = WHERE_DICT.get(kwargs.pop("where", "pre"), None)
260
- settings = [f"const plot mark {where}"]
381
+ settings = {f"const plot mark {where}": None}
261
382
  if len(args) == 1:
262
383
  kwargs["fmt"] = args[0]
263
384
  return self._plot(x,y,settings=settings, **kwargs)
@@ -266,7 +387,7 @@ class BaseAxes:
266
387
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
267
388
  kwargs = self._check_kwargs("axvline", kws, **kwargs)
268
389
  self._ext_ymin = self._ext_ymax = True
269
- self._plot(x, (ymin, ymax), settings="axvline", **kwargs)
390
+ self._plot(x, (ymin, ymax), settings={"axvline": None}, **kwargs)
270
391
 
271
392
  def axhline(self, y, xmin=0, xmax=1, **kwargs):
272
393
  kws = {"fmt", "base", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "label"}
@@ -275,18 +396,18 @@ class BaseAxes:
275
396
  self._primary._ext_xmin = self._primary._ext_xmax = True
276
397
  else:
277
398
  self._ext_xmin = self._ext_xmax = True
278
- self._plot((xmin, xmax), y, settings="axhline", **kwargs)
399
+ self._plot((xmin, xmax), y, settings={"axhline": None}, **kwargs)
279
400
 
280
401
  def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
281
- kws = {"c", "color", "alpha", "label"}
402
+ kws = {"c", "color", "alpha", "label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
282
403
  kwargs = self._check_kwargs("axvspan", kws, **kwargs)
283
404
  self._ext_ymin = self._ext_ymax = True
284
405
  if TikzConfig.USE_GROUPPLOTS:
285
406
  self._axis_args.add("set layers")
286
- self._plot([xmin, xmax], [ymin, ymax], settings="axvspan", **kwargs)
407
+ self._plot([xmin, xmax], [ymin, ymax], settings={"axvspan": None}, **kwargs)
287
408
 
288
409
  def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
289
- kws = {"c", "color", "alpha", "label"}
410
+ kws = {"c", "color", "alpha", "label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
290
411
  kwargs = self._check_kwargs("axhspan", kws, **kwargs)
291
412
  if isinstance(self, Secondary):
292
413
  self._primary._ext_xmin = self._primary._ext_xmax = True
@@ -294,7 +415,7 @@ class BaseAxes:
294
415
  self._ext_xmin = self._ext_xmax = True
295
416
  if TikzConfig.USE_GROUPPLOTS:
296
417
  self._axis_args.add("set layers")
297
- self._plot([xmin, xmax], [ymin, ymax], settings="axhspan", **kwargs)
418
+ self._plot([xmin, xmax], [ymin, ymax], settings={"axhspan": None}, **kwargs)
298
419
 
299
420
  def set_ylabel(self, label):
300
421
  self._axis_options["ylabel"] = f"{{{tex_text(label)}}}"
@@ -354,7 +475,19 @@ class BaseAxes:
354
475
  _LEGEND_LOC_MAP = ["best", "upper right", "upper left", "lower_left", "lower right", "right", "center left", "center right", "lower center", "upper center", "center"]
355
476
  _ANCHOR_MAP = {"top": "north", "bottom": "south", "upper": "north", "lower": "south", "left": "west", "right": "east", "center": "center"}
356
477
 
478
+ def _match_color(self, input):
479
+ if isinstance(self, Axes) or isinstance(self, Secondary):
480
+ if input == "none":
481
+ return "none"
482
+ ccode, op = _tex_color(input, self._style)
483
+ if isinstance(ccode, str):
484
+ return ccode
485
+ r,g,b = ccode
486
+ self._add_col(r,g,b)
487
+ return f"c{r:.3f}{g:.3f}{b:.3f}".replace(".", "")
488
+
357
489
  def legend(self, *args, **kwargs):
490
+ legend_string = {}
358
491
  if "loc" in kwargs:
359
492
  loc = kwargs["loc"]
360
493
  lx = ly = posit = None
@@ -381,20 +514,34 @@ class BaseAxes:
381
514
  elif "east" in posit:
382
515
  lx = 1 - TikzConfig.LEGEND_REL_X
383
516
 
384
- legend_string = []
385
517
  if lx is not None and ly is not None:
386
- legend_string.append(r"at={(" + f"{lx},{ly}" + r")}")
518
+ legend_string["at"] = "{(" + f"{lx},{ly}" + r")}"
387
519
  if posit is not None and len(posit):
388
- legend_string.append(r"anchor=" + posit)
389
- if "legend style" in self._axis_options:
390
- self._axis_options["legend style"] = self._axis_options["legend style"].rstrip("}") + f" ,{','.join(legend_string)}}}"
391
- else:
392
- self._axis_options["legend style"] = f"{{{','.join(legend_string)}}}"
520
+ legend_string["anchor"] = posit
521
+
522
+ if "facecolor" in kwargs and (isinstance(self, Axes) or isinstance(self, Secondary)):
523
+ ccode = self._match_color(kwargs["facecolor"])
524
+ if ccode is not None:
525
+ legend_string["fill"] = ccode
526
+ if "edgecolor" in kwargs and (isinstance(self, Axes) or isinstance(self, Secondary)):
527
+ ccode = self._match_color(kwargs["edgecolor"])
528
+ if ccode is not None:
529
+ legend_string["draw"] = ccode
530
+ if "labelcolor" in kwargs and (isinstance(self, Axes) or isinstance(self, Secondary)):
531
+ ccode = self._match_color(kwargs["labelcolor"])
532
+ if ccode is not None:
533
+ self._legend_lab_col = ccode
534
+ if "frameon" in kwargs and not kwargs["frameon"]:
535
+ legend_string["draw"] = "none"
536
+ if "legend style" in self._axis_options:
537
+ self._axis_options["legend style"].update(legend_string)
538
+ else:
539
+ self._axis_options["legend style"] = legend_string
393
540
  self._legend_on = True
394
541
  if "ncols" in kwargs:
395
542
  self._axis_options["legend columns"] = kwargs["ncols"]
396
543
  if len(args) == 2:
397
- self._add_legend = args
544
+ self._add_legend = list(args)
398
545
  elif len(args) == 1:
399
546
  labs = args[0]
400
547
  if len(labs) > len(self._elements):
@@ -433,16 +580,36 @@ class BaseAxes:
433
580
  return ""
434
581
  for i in range(len(axs)):
435
582
  output += f"\n\\addlegendimage{{{axs[i]._style_string()}}}"
436
- output += f"\n\\addlegendentry{{{tex_text(labs[i])}}}"
583
+ if self._legend_lab_col:
584
+ output += f"\n\\addlegendentry[{self._legend_lab_col}]{{{tex_text(labs[i])}}}"
585
+ else:
586
+ output += f"\n\\addlegendentry{{{tex_text(labs[i])}}}"
437
587
  return output
438
588
 
439
589
  def _content_tex(self, filename):
440
- ouptut = "\n".join(e._to_tex(filename) for e in self._elements)
441
- ouptut += self._add_legend_entries()
590
+ element_strings = {id(e): e._to_tex(filename, self._legend_lab_col) for e in self._elements}
591
+ seq_map = {seq[0]: seq for seq in self._reverse_elements}
592
+ output_list = []
593
+ visited = set()
594
+ for e in self._elements:
595
+ e_id = id(e)
596
+ if e_id in visited:
597
+ continue
598
+ if e_id in seq_map:
599
+ seq = seq_map[e_id]
600
+ for el in reversed(seq):
601
+ output_list.append(element_strings[el])
602
+ visited.add(el)
603
+ else:
604
+ output_list.append(element_strings[e_id])
605
+ visited.add(e_id)
606
+
607
+ output = "\n".join(output_list)
608
+ output += self._add_legend_entries()
442
609
  for coord in self._coordinates:
443
610
  x,y = self._coordinates[coord]
444
- ouptut += f"\n\\coordinate ({coord}) at ({x},{y});"
445
- return ouptut
611
+ output += f"\n\\coordinate ({coord}) at ({x},{y});"
612
+ return output
446
613
 
447
614
  def _get_hard_range(self,which):
448
615
  arg = f"{which[0]}mode"
@@ -519,6 +686,10 @@ class BaseAxes:
519
686
  if attr in kwargs:
520
687
  defined[attr](kwargs.pop(attr))
521
688
 
689
+ def _reoreder_last_elements(self, n):
690
+ if n <= 0: return
691
+ self._elements = self._elements[:-n] + self._elements[:-n-1:-1]
692
+
522
693
  class Axes(BaseAxes):
523
694
 
524
695
  def __init__(self, nrows, ncols, index, fig, polar):
@@ -654,7 +825,7 @@ class Axes(BaseAxes):
654
825
  if kwargs:
655
826
  accepted_kwargs = {"color", "c", "linestyle", "ls", "linewidth", "lw", "alpha"}
656
827
  kwargs = self._check_kwargs("grid", accepted_kwargs, **kwargs)
657
- g = Graph(self, None, [], None, None, **kwargs)._style_string()
828
+ g = Graph(self, None, {}, None, None, **kwargs)._style_string()
658
829
  self._axis_options[f"{selector}grid style"] = f"{{{g}}}"
659
830
 
660
831
  def set_minorticks_num(self, num):
@@ -733,7 +904,7 @@ class Axes(BaseAxes):
733
904
  _plt.axis("off")
734
905
  _plt.imshow(*args, **kwargs)
735
906
  im_name = f"{str(main_name()[1]).removesuffix('.py')}_{TikzConfig.IMSHOW_SAVENAME}{_next_imshow_num()}.pdf"
736
- _plt.savefig(im_name,bbox_inches='tight', pad_inches=0)
907
+ _plt.savefig(im_name, bbox_inches='tight', pad_inches=0)
737
908
  return im_name
738
909
 
739
910
  def _axis_option_string(self):
@@ -754,7 +925,7 @@ class Axes(BaseAxes):
754
925
  if "extent" in self._imshow[1]:
755
926
  bounds = self._imshow[1]["extent"]
756
927
  xm, xM, ym, yM = bounds
757
- self._elements.insert(0, Graph(self, f"graphics [xmin={xm}, xmax={xM}, ymin={ym}, ymax={yM}] {{{im_name}}}",settings=[], xerr=None, yerr=None, onlayer="axis background"))
928
+ self._elements.insert(0, Graph(self, f"graphics [xmin={xm}, xmax={xM}, ymin={ym}, ymax={yM}] {{{im_name}}}", settings={}, xerr=None, yerr=None, onlayer="axis background"))
758
929
  axis_opt_str = ""
759
930
  if self._axis_args:
760
931
  axis_opt_str += ",\n".join(self._axis_args)
@@ -787,7 +958,13 @@ class Axes(BaseAxes):
787
958
  self._axis_options["ymax"] = self._fig._next_limname("ymax", self._axis_options.get("ymax", yM))
788
959
  if self._axis_options:
789
960
  if axis_opt_str: axis_opt_str += ",\n"
790
- axis_opt_str += ",\n".join(f"{k}={v}" for k, v in self._axis_options.items())
961
+ for k, v in self._axis_options.items():
962
+ if isinstance(v, dict):
963
+ axis_opt_str += f"{k}={{"
964
+ axis_opt_str += ",\n".join(f"{kk}={vv}" for kk, vv in v.items())
965
+ axis_opt_str += "},\n"
966
+ else:
967
+ axis_opt_str += f"{k}={v},\n"
791
968
  if self._colorbar:
792
969
  axis_opt_str += self._colorbar
793
970
  elif self._cmap_bar:
@@ -859,6 +1036,15 @@ class Axes(BaseAxes):
859
1036
  defined[attr](kwargs.pop(attr))
860
1037
 
861
1038
  super().set(**kwargs)
1039
+
1040
+ def set_facecolor(self, color):
1041
+ ccode, _ = _tex_color(color, self._style)
1042
+ if isinstance(ccode, str):
1043
+ self._axis_options["axis background/.style"] = f"{{fill={ccode}}}"
1044
+ else:
1045
+ r,g,b = ccode
1046
+ self._add_col(r,g,b)
1047
+ self._axis_options["axis background/.style"] = f"{{fill=c{r:.3f}{g:.3f}{b:.3f}}}".replace(".", "")
862
1048
 
863
1049
  class Secondary(BaseAxes):
864
1050
  def __init__(self, primary):
@@ -900,7 +1086,13 @@ class Secondary(BaseAxes):
900
1086
  self._axis_options["xmax"] = self._primary._axis_options["xmax"]
901
1087
  if self._axis_options:
902
1088
  if axis_opt_str: axis_opt_str += ",\n"
903
- axis_opt_str += ",\n".join(f"{k}={v}" for k, v in self._axis_options.items())
1089
+ for k, v in self._axis_options.items():
1090
+ if isinstance(v, dict):
1091
+ axis_opt_str += f"{k}={{"
1092
+ axis_opt_str += ",\n".join(f"{kk}={vv}" for kk, vv in v.items())
1093
+ axis_opt_str += "},\n"
1094
+ else:
1095
+ axis_opt_str += f"{k}={v},\n"
904
1096
  return axis_opt_str
905
1097
 
906
1098
  def _padding(self):