tikzplot42 0.5.1__tar.gz → 0.5.2__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.
- {tikzplot42-0.5.1/src/tikzplot42.egg-info → tikzplot42-0.5.2}/PKG-INFO +14 -1
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/README.md +13 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/pyproject.toml +1 -1
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/axes.py +248 -37
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/axes.pyi +128 -21
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/elements.py +73 -51
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/figure.pyi +1 -1
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/plots.py +14 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/plots.pyi +102 -8
- {tikzplot42-0.5.1 → tikzplot42-0.5.2/src/tikzplot42.egg-info}/PKG-INFO +14 -1
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot42.egg-info/SOURCES.txt +1 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test14.py +1 -1
- tikzplot42-0.5.2/tests/test16.py +24 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/LICENSE +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/setup.cfg +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/__init__.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/__init__.pyi +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/axes3d.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/axes3d.pyi +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/colorbar.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/colorbar.pyi +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/colors.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/config.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/config.pyi +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/elements.pyi +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/figure.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/latex_special.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/py.typed +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/state.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/styles.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/styles.pyi +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot/texts.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot42.egg-info/dependency_links.txt +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot42.egg-info/requires.txt +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/src/tikzplot42.egg-info/top_level.txt +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test1.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test10.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test11.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test12.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test13.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test15.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test2.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test3.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test4.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test5.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test6.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test7.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test8.py +0 -0
- {tikzplot42-0.5.1 → tikzplot42-0.5.2}/tests/test9.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tikzplot42
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -706,6 +706,18 @@ Please report any bugs or unexpected behaviour in Issues. Examples may be found
|
|
|
706
706
|
<img src="https://raw.githubusercontent.com/ZanAmb/TikzPlot/main/tests/demo.png" width="60%">
|
|
707
707
|
</p>
|
|
708
708
|
|
|
709
|
+
## Why use pgfplots instead of matplotlib?
|
|
710
|
+
- Tikz/pgfplots is a LaTeX package, so it is easy to integrate into your LaTeX document, and the fonts will match your document, rescaling will not affect font sizes.
|
|
711
|
+
- Tikz/pgfplots graph is easy to edit, especially if only minor style changes are needed, while matplotlib requires re-running the code to generate a new graph.
|
|
712
|
+
- Full math mode is supported, so you can use LaTeX math in your labels, legends, etc.
|
|
713
|
+
|
|
714
|
+
## Why use this package instead of other available?
|
|
715
|
+
- It is easy to use, with code being very similar to matplotlib and only minor changes are needed to switch from matplotlib to this package.
|
|
716
|
+
- It relies on frontend commands, which is less likley to change in matplotlib, so new updates do not break it.
|
|
717
|
+
- It generates clean and readable Tikz code, which is easy to edit and understand. Code is also efficient and does not contain unnecessary commands, which is important escpecially for large graphs.
|
|
718
|
+
- If you use LLMs to generate pgfplots code, you will often get code that is not compilable, since for more complex requirements, LLMs make up commands that do not exist. This package covers many use cases and generates compilable code.
|
|
719
|
+
|
|
720
|
+
|
|
709
721
|
# Installation
|
|
710
722
|
PyPI: `pip install tikzplot42`.
|
|
711
723
|
Alternativley, download this package and install using: `pip install [path]`, where [path] is the path to the directory, containing `pyproject.toml`.
|
|
@@ -741,6 +753,7 @@ Some basic plot commands are already implemented with commonly used arguments:
|
|
|
741
753
|
- `hist()`,
|
|
742
754
|
- `bar()/barh()`,
|
|
743
755
|
- `bar_label()`,
|
|
756
|
+
- `stackplot()`,
|
|
744
757
|
- `step()`,
|
|
745
758
|
- `imshow()`,
|
|
746
759
|
- `text()`,
|
|
@@ -7,6 +7,18 @@ Please report any bugs or unexpected behaviour in Issues. Examples may be found
|
|
|
7
7
|
<img src="https://raw.githubusercontent.com/ZanAmb/TikzPlot/main/tests/demo.png" width="60%">
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
+
## Why use pgfplots instead of matplotlib?
|
|
11
|
+
- Tikz/pgfplots is a LaTeX package, so it is easy to integrate into your LaTeX document, and the fonts will match your document, rescaling will not affect font sizes.
|
|
12
|
+
- Tikz/pgfplots graph is easy to edit, especially if only minor style changes are needed, while matplotlib requires re-running the code to generate a new graph.
|
|
13
|
+
- Full math mode is supported, so you can use LaTeX math in your labels, legends, etc.
|
|
14
|
+
|
|
15
|
+
## Why use this package instead of other available?
|
|
16
|
+
- It is easy to use, with code being very similar to matplotlib and only minor changes are needed to switch from matplotlib to this package.
|
|
17
|
+
- It relies on frontend commands, which is less likley to change in matplotlib, so new updates do not break it.
|
|
18
|
+
- It generates clean and readable Tikz code, which is easy to edit and understand. Code is also efficient and does not contain unnecessary commands, which is important escpecially for large graphs.
|
|
19
|
+
- If you use LLMs to generate pgfplots code, you will often get code that is not compilable, since for more complex requirements, LLMs make up commands that do not exist. This package covers many use cases and generates compilable code.
|
|
20
|
+
|
|
21
|
+
|
|
10
22
|
# Installation
|
|
11
23
|
PyPI: `pip install tikzplot42`.
|
|
12
24
|
Alternativley, download this package and install using: `pip install [path]`, where [path] is the path to the directory, containing `pyproject.toml`.
|
|
@@ -42,6 +54,7 @@ Some basic plot commands are already implemented with commonly used arguments:
|
|
|
42
54
|
- `hist()`,
|
|
43
55
|
- `bar()/barh()`,
|
|
44
56
|
- `bar_label()`,
|
|
57
|
+
- `stackplot()`,
|
|
45
58
|
- `step()`,
|
|
46
59
|
- `imshow()`,
|
|
47
60
|
- `text()`,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tikzplot42"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
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" }
|
|
@@ -4,6 +4,7 @@ import copy
|
|
|
4
4
|
|
|
5
5
|
import numpy as _np
|
|
6
6
|
import matplotlib.pyplot as _plt
|
|
7
|
+
from scipy import datasets
|
|
7
8
|
|
|
8
9
|
from .elements import Graph
|
|
9
10
|
from .texts import Text
|
|
@@ -21,7 +22,7 @@ class BaseAxes:
|
|
|
21
22
|
self._legend_on = False
|
|
22
23
|
self._overlay_legend = False
|
|
23
24
|
self._overlay_legend_entries = []
|
|
24
|
-
self._overlay_special = {}
|
|
25
|
+
self._overlay_special: dict[int, dict[str, Any]] = {}
|
|
25
26
|
self._yticks = True
|
|
26
27
|
self._fig = None
|
|
27
28
|
if TikzConfig.USE_DECIMAL_COMMA:
|
|
@@ -145,10 +146,17 @@ class BaseAxes:
|
|
|
145
146
|
self._axis_options[key].update(value)
|
|
146
147
|
self._axis_options[key] = value
|
|
147
148
|
|
|
149
|
+
def _parse_entry(self, k, v):
|
|
150
|
+
if v is None:
|
|
151
|
+
return f"{k}"
|
|
152
|
+
if isinstance(v, dict):
|
|
153
|
+
return f"{k}={{" + ",\n".join(f"{kk}={vv}" for kk, vv in v.items() if vv != {}) + "}"
|
|
154
|
+
return f"{k}={v}"
|
|
155
|
+
|
|
148
156
|
def _plot(self, x, y, settings={}, xerr=None, yerr=None, overlay=None, note=None, **style):
|
|
149
157
|
spec = None
|
|
150
158
|
if self._get_overlay() in self._overlay_special:
|
|
151
|
-
spec = self._overlay_special[self._get_overlay()]
|
|
159
|
+
spec = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special[self._get_overlay()].items()])
|
|
152
160
|
if note != spec:
|
|
153
161
|
self._get_free_overlay()
|
|
154
162
|
|
|
@@ -256,7 +264,7 @@ class BaseAxes:
|
|
|
256
264
|
return self._plot(y,x,settings={"xcomb": None}, **kwargs)
|
|
257
265
|
|
|
258
266
|
def fill_between(self, x, y1, y2=None, **kwargs):
|
|
259
|
-
kws = {"fmt", "alpha", "color", "c", "label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
|
|
267
|
+
kws = {"fmt", "alpha", "color", "c", "facecolor", "fc", "label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
|
|
260
268
|
kwargs = self._check_kwargs("fill_between", kws, **kwargs)
|
|
261
269
|
def _check_instance(xs, ys, pname):
|
|
262
270
|
for el in self._elements[self._get_overlay()]:
|
|
@@ -277,7 +285,7 @@ class BaseAxes:
|
|
|
277
285
|
name1 = inst
|
|
278
286
|
|
|
279
287
|
if y2 is not None:
|
|
280
|
-
if isinstance(
|
|
288
|
+
if isinstance(y2, (int, float)):
|
|
281
289
|
y2 = _np.asarray([y2] * len(x))
|
|
282
290
|
inst = _check_instance(x,y2,name2)
|
|
283
291
|
if inst is None:
|
|
@@ -292,6 +300,59 @@ class BaseAxes:
|
|
|
292
300
|
self._plot(xs,ys,path_name=name2, alpha=0)
|
|
293
301
|
else:
|
|
294
302
|
name2 = inst
|
|
303
|
+
if "facecolor" in kwargs or "fc" in kwargs:
|
|
304
|
+
kwargs["color"] = kwargs.pop("facecolor", kwargs.pop("fc", None))
|
|
305
|
+
kwargs.pop("fc", None)
|
|
306
|
+
if not("color" in kwargs or "c" in kwargs):
|
|
307
|
+
assert isinstance(self, Axes) or isinstance(self, Secondary)
|
|
308
|
+
kwargs["color"] = f"C{self._get_defcol(1)}"
|
|
309
|
+
e = Graph(self, f"fill between [of={name1} and {name2}]",settings={}, xerr=None, yerr=None, **kwargs)
|
|
310
|
+
self._elements[self._get_overlay()].append(e)
|
|
311
|
+
return e
|
|
312
|
+
|
|
313
|
+
def fill_betweenx(self, y, x1, x2=None, **kwargs):
|
|
314
|
+
kws = {"fmt", "alpha", "color", "c", "facecolor", "fc", "label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
|
|
315
|
+
kwargs = self._check_kwargs("fill_between", kws, **kwargs)
|
|
316
|
+
def _check_instance(xs, ys, pname):
|
|
317
|
+
for el in self._elements[self._get_overlay()]:
|
|
318
|
+
if el._check_equal(xs,ys):
|
|
319
|
+
return el._try_set_pname(pname)
|
|
320
|
+
else:
|
|
321
|
+
return None
|
|
322
|
+
assert self._fig is not None
|
|
323
|
+
self._fig._add_required_package("\\usepgfplotslibrary{fillbetween}")
|
|
324
|
+
name1 = self._fig._get_free_path_name()
|
|
325
|
+
name2 = self._fig._get_free_path_name()
|
|
326
|
+
if isinstance(x1, (int, float)):
|
|
327
|
+
x1 = _np.asarray([x1] * len(y))
|
|
328
|
+
inst = _check_instance(x1,y,name1)
|
|
329
|
+
if inst is None:
|
|
330
|
+
self._plot(x1,y,path_name=name1, alpha=0)
|
|
331
|
+
else:
|
|
332
|
+
name1 = inst
|
|
333
|
+
|
|
334
|
+
if x2 is not None:
|
|
335
|
+
if isinstance(x2, (int, float)):
|
|
336
|
+
x2 = _np.asarray([x2] * len(y))
|
|
337
|
+
inst = _check_instance(x2,y,name2)
|
|
338
|
+
if inst is None:
|
|
339
|
+
self._plot(x2,y,path_name=name2, alpha=0)
|
|
340
|
+
else:
|
|
341
|
+
name2 = inst
|
|
342
|
+
else:
|
|
343
|
+
xs = [0, 0]
|
|
344
|
+
ys = [min(y), max(y)]
|
|
345
|
+
inst = _check_instance(xs,ys,name2)
|
|
346
|
+
if inst is None:
|
|
347
|
+
self._plot(xs,ys,path_name=name2, alpha=0)
|
|
348
|
+
else:
|
|
349
|
+
name2 = inst
|
|
350
|
+
if "facecolor" in kwargs or "fc" in kwargs:
|
|
351
|
+
kwargs["color"] = kwargs.pop("facecolor", kwargs.pop("fc", None))
|
|
352
|
+
kwargs.pop("fc", None)
|
|
353
|
+
if not("color" in kwargs or "c" in kwargs):
|
|
354
|
+
assert isinstance(self, Axes) or isinstance(self, Secondary)
|
|
355
|
+
kwargs["color"] = f"C{self._get_defcol(1)}"
|
|
295
356
|
e = Graph(self, f"fill between [of={name1} and {name2}]",settings={}, xerr=None, yerr=None, **kwargs)
|
|
296
357
|
self._elements[self._get_overlay()].append(e)
|
|
297
358
|
return e
|
|
@@ -409,13 +470,6 @@ class BaseAxes:
|
|
|
409
470
|
offset = widths.mean() / (len(datasets) + 1)
|
|
410
471
|
else:
|
|
411
472
|
intervals = True
|
|
412
|
-
if "range" in kwargs:
|
|
413
|
-
if orientation == "horizontal":
|
|
414
|
-
self.set_ylim(kwargs["range"])
|
|
415
|
-
elif isinstance(self, Axes):
|
|
416
|
-
self.set_xlim(kwargs["range"])
|
|
417
|
-
elif isinstance(self, Secondary):
|
|
418
|
-
self._primary.set_xlim(kwargs["range"])
|
|
419
473
|
base_settings = settings.copy()
|
|
420
474
|
outputs = []
|
|
421
475
|
totals, _ = _np.histogram(all_data, edges, density=False, weights=kwargs.get("weights", None), range=kwargs.get("range", None))
|
|
@@ -489,24 +543,112 @@ class BaseAxes:
|
|
|
489
543
|
outputs.append(e)
|
|
490
544
|
return outputs
|
|
491
545
|
|
|
492
|
-
def bar(self, x, height,
|
|
493
|
-
kws = {"alpha", "
|
|
546
|
+
def bar(self, x, height, width=0.8, bottom=None, **kwargs):
|
|
547
|
+
kws = {"alpha", "align", "color", "c", "facecolor", "fc", "edgecolor", "ec", "linewidth", "lw", "tick_label", "label", "xerr", "yerr", "ecolor", "capsize", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance", "tick_label"}
|
|
494
548
|
kwargs = self._check_kwargs("bar", kws, **kwargs)
|
|
549
|
+
kwargs["width"] = width
|
|
550
|
+
if bottom is not None:
|
|
551
|
+
kwargs["bottom"] = bottom
|
|
495
552
|
if "width" in kwargs:
|
|
496
553
|
kwargs["thickness"] = kwargs.pop("width")
|
|
497
554
|
kwargs["edge"] = kwargs.pop("bottom", 0)
|
|
498
555
|
|
|
499
556
|
return self._common_bar(x, height, orientation="vertical", **kwargs)
|
|
500
557
|
|
|
501
|
-
def barh(self, y, width,
|
|
502
|
-
kws = {"alpha", "
|
|
558
|
+
def barh(self, y, width, height=0.8, left=None, **kwargs):
|
|
559
|
+
kws = {"alpha", "align", "color", "c", "facecolor", "fc", "edgecolor", "ec", "linewidth", "lw", "tick_label", "label", "xerr", "yerr", "ecolor", "capsize", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
|
|
503
560
|
kwargs = self._check_kwargs("barh", kws, **kwargs)
|
|
561
|
+
kwargs["height"] = height
|
|
562
|
+
if left is not None:
|
|
563
|
+
kwargs["left"] = left
|
|
504
564
|
if "height" in kwargs:
|
|
505
565
|
kwargs["thickness"] = kwargs.pop("height")
|
|
506
566
|
kwargs["edge"] = kwargs.pop("left", 0)
|
|
507
567
|
|
|
508
568
|
return self._common_bar(y, width, orientation="horizontal", **kwargs)
|
|
509
569
|
|
|
570
|
+
def grouped_bar(self, heights, **kwargs):
|
|
571
|
+
kws = {"positions", "tick_labels", "labels", "group_spacing", "bar_spacing", "orientation", "colors", "alpha", "edgecolor", "ec", "facecolor", "fc", "linewidth", "lw", "linestyle", "ls", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
|
|
572
|
+
kwargs = self._check_kwargs("grouped_bar", kws, **kwargs)
|
|
573
|
+
if isinstance(heights, dict):
|
|
574
|
+
if "labels" not in kwargs:
|
|
575
|
+
kwargs["labels"] = list(heights.keys())
|
|
576
|
+
heights = list(heights.values())
|
|
577
|
+
if isinstance(heights, (list, tuple)) and len(heights) > 0 and isinstance(heights[0], (list, tuple, _np.ndarray)):
|
|
578
|
+
try:
|
|
579
|
+
datasets = [_np.asarray(ds, dtype=_np.float64) for ds in heights]
|
|
580
|
+
if any(ds.ndim != 1 for ds in datasets):
|
|
581
|
+
raise ValueError("Nested datasets must all be 1-dimensional.")
|
|
582
|
+
except (ValueError, TypeError):
|
|
583
|
+
h_arr = _np.asarray(heights)
|
|
584
|
+
if h_arr.ndim == 2:
|
|
585
|
+
datasets = [h_arr[:, i] for i in range(h_arr.shape[1])]
|
|
586
|
+
else:
|
|
587
|
+
raise ValueError("Invalid dataset structure.")
|
|
588
|
+
else:
|
|
589
|
+
h_arr = _np.asarray(heights)
|
|
590
|
+
if h_arr.ndim == 1:
|
|
591
|
+
datasets = [h_arr]
|
|
592
|
+
elif h_arr.ndim == 2:
|
|
593
|
+
datasets = [h_arr[:, i] for i in range(h_arr.shape[1])]
|
|
594
|
+
else:
|
|
595
|
+
raise ValueError(f"Input must be 1D or 2D, got {h_arr.ndim}D.")
|
|
596
|
+
|
|
597
|
+
xs = kwargs.pop("positions", range(len(datasets[0])))
|
|
598
|
+
if len(xs) != len(datasets[0]):
|
|
599
|
+
raise Warning("Length of positions does not match length of datasets.")
|
|
600
|
+
if _np.diff(xs).min() != _np.diff(xs).max():
|
|
601
|
+
raise Warning("Positions must be equidistant.")
|
|
602
|
+
dist = _np.diff(xs).min()
|
|
603
|
+
settings = {}
|
|
604
|
+
if "rwidth" not in kwargs:
|
|
605
|
+
kwargs["rwidth"] = 1
|
|
606
|
+
orientation = kwargs.pop("orientation", "vertical")
|
|
607
|
+
group_spacing = kwargs.pop("group_spacing", 1.5)
|
|
608
|
+
bar_spacing = kwargs.pop("bar_spacing", 0.0)
|
|
609
|
+
bar_widths_num = len(datasets) * (1 + bar_spacing) + group_spacing
|
|
610
|
+
bar_width = dist / bar_widths_num
|
|
611
|
+
settings["thickness"] = bar_width
|
|
612
|
+
|
|
613
|
+
if "tick_labels" in kwargs:
|
|
614
|
+
tick_labels = kwargs.pop("tick_labels")
|
|
615
|
+
if len(tick_labels) != len(xs):
|
|
616
|
+
raise Warning("Length of tick_labels does not match length of positions.")
|
|
617
|
+
if orientation == "vertical":
|
|
618
|
+
if isinstance(self, Axes):
|
|
619
|
+
self.set_xticks(xs, tick_labels)
|
|
620
|
+
elif isinstance(self, Secondary):
|
|
621
|
+
self._primary.set_xticks(xs, tick_labels)
|
|
622
|
+
else:
|
|
623
|
+
self.set_yticks(xs, tick_labels)
|
|
624
|
+
|
|
625
|
+
datas = {}
|
|
626
|
+
for kw in ["colors", "alpha", "facecolor", "fc", "edgecolor", "ec", "labels", "linestyle", "ls", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"]:
|
|
627
|
+
if kw in kwargs:
|
|
628
|
+
if isinstance(kwargs[kw], (list)):
|
|
629
|
+
if len(kwargs[kw]) != len(datasets):
|
|
630
|
+
raise Warning(f"Length of {kw} does not match number of datasets.")
|
|
631
|
+
prop = kwargs.pop(kw)
|
|
632
|
+
else:
|
|
633
|
+
prop = [kwargs.pop(kw)] * len(datasets)
|
|
634
|
+
for i in range(len(datasets)):
|
|
635
|
+
if i not in datas:
|
|
636
|
+
datas[i] = {}
|
|
637
|
+
datas[i][kw.removesuffix("s")] = prop[i]
|
|
638
|
+
base_settings = settings.copy()
|
|
639
|
+
bars = []
|
|
640
|
+
for i in range(len(datasets)):
|
|
641
|
+
offset = (i - len(datasets)/2 + 0.5) * bar_width * (1 + bar_spacing)
|
|
642
|
+
data = datasets[i]
|
|
643
|
+
settings = base_settings.copy()
|
|
644
|
+
kws = datas.get(i, {})
|
|
645
|
+
passing_args = ["alpha", "facecolor", "fc", "color", "c", "edgecolor", "ec", "label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"]
|
|
646
|
+
for a in passing_args:
|
|
647
|
+
if a in kws:
|
|
648
|
+
settings[a] = kws[a]
|
|
649
|
+
bars.append(self._common_bar(xs, data, orientation=orientation, **settings, group_offset=offset))
|
|
650
|
+
return bars
|
|
651
|
+
|
|
510
652
|
def _common_bar(self, k, v, orientation="vertical", **kwargs):
|
|
511
653
|
#if orientation == "vertical":
|
|
512
654
|
# self._int_ymax = self._int_ymin = 0
|
|
@@ -593,7 +735,7 @@ class BaseAxes:
|
|
|
593
735
|
|
|
594
736
|
if _np.count_nonzero(edge) > 0:
|
|
595
737
|
lower = self._get_overlay()
|
|
596
|
-
if self._overlay_special.get(lower,
|
|
738
|
+
if bar_type + " stacked" in self._overlay_special.get(lower, {}):
|
|
597
739
|
bedx = _np.zeros_like(edge, dtype=_np.float64)
|
|
598
740
|
bedy = _np.zeros_like(edge, dtype=_np.float64)
|
|
599
741
|
for el in self._elements[lower]:
|
|
@@ -612,16 +754,18 @@ class BaseAxes:
|
|
|
612
754
|
if overlay is None:
|
|
613
755
|
if len(self._elements[self._get_overlay()]) > 0 and self._elements[self._get_overlay()][-1]._check_equal(k, edge):
|
|
614
756
|
if len(self._elements[lower]) == 1:
|
|
615
|
-
|
|
757
|
+
if lower not in self._overlay_special:
|
|
758
|
+
self._overlay_special[lower] = {}
|
|
759
|
+
self._overlay_special[lower].update({bar_type + " stacked": None})
|
|
616
760
|
overlay = lower
|
|
617
761
|
else:
|
|
618
762
|
current = self._get_free_overlay()
|
|
619
763
|
self._elements[current].append(self._elements[lower].pop()) # move the stacking bed to new overlay
|
|
620
|
-
self._overlay_special[current] = bar_type + " stacked"
|
|
764
|
+
self._overlay_special[current] = {bar_type + " stacked": None}
|
|
621
765
|
overlay = current
|
|
622
766
|
else: # no bed, plot invisible bed
|
|
623
767
|
current = self._get_free_overlay()
|
|
624
|
-
self._overlay_special[current] = bar_type + " stacked"
|
|
768
|
+
self._overlay_special[current] = {bar_type + " stacked": None}
|
|
625
769
|
self._plot(k, edge, alpha=0)
|
|
626
770
|
overlay = current
|
|
627
771
|
|
|
@@ -638,7 +782,7 @@ class BaseAxes:
|
|
|
638
782
|
for kw in ["label", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"]:
|
|
639
783
|
if kw in kwargs:
|
|
640
784
|
element_args[kw] = kwargs.pop(kw)
|
|
641
|
-
note = self._overlay_special.get(overlay, None
|
|
785
|
+
note = ",\n".join([self._parse_entry(k, v) for k, v in self._overlay_special.get(overlay, {}).items()]) if overlay in self._overlay_special else None
|
|
642
786
|
if "label" in element_args:
|
|
643
787
|
if orientation == "vertical":
|
|
644
788
|
settings["ybar legend"] = None
|
|
@@ -648,7 +792,7 @@ class BaseAxes:
|
|
|
648
792
|
return self._plot(k, v, yerr=kwargs.get("yerr", None), xerr=kwargs.get("xerr", None), overlay=overlay, settings=settings, note=note, **element_args)
|
|
649
793
|
|
|
650
794
|
def bar_label(self, container, labels=None, **kwargs):
|
|
651
|
-
kws = {"color", "c", "fontsize", "rotation", "fmt", "padding"}
|
|
795
|
+
kws = {"alpha", "color", "c", "fontsize", "rotation", "fmt", "padding"}
|
|
652
796
|
kwargs = self._check_kwargs("bar_label", kws, **kwargs)
|
|
653
797
|
if not isinstance(container, Graph):
|
|
654
798
|
raise Warning("Container must be a Graph object.")
|
|
@@ -659,6 +803,8 @@ class BaseAxes:
|
|
|
659
803
|
if "color" in kwargs or "c" in kwargs:
|
|
660
804
|
c = kwargs.pop("color", kwargs.pop("c", None))
|
|
661
805
|
kwargs["color_parsed"] = self._match_color(c)
|
|
806
|
+
if "alpha" in kwargs:
|
|
807
|
+
kwargs["opacity"] = kwargs.pop("alpha")
|
|
662
808
|
if "fontsize" in kwargs:
|
|
663
809
|
kwargs["fontsize_parsed"] = self._tex_fontsize(kwargs["fontsize"])
|
|
664
810
|
o = self._get_element_overlay(container)
|
|
@@ -683,7 +829,68 @@ class BaseAxes:
|
|
|
683
829
|
if labels is not None and labels != []:
|
|
684
830
|
container._add_meta_column(labels)
|
|
685
831
|
self._bar_labels[self._get_element_overlay(container)][container] = kwargs | {"bar_labels": labels}
|
|
686
|
-
|
|
832
|
+
|
|
833
|
+
def stackplot(self, x, *args, **kwargs):
|
|
834
|
+
kws = {"baseline", "labels", "colors", "alpha", "facecolor", "fc", "edgecolor", "ec", "linewidth", "lw", "linestyle", "ls", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"}
|
|
835
|
+
kwargs = self._check_kwargs("stackplot", kws, **kwargs)
|
|
836
|
+
n = len(x)
|
|
837
|
+
x = _np.asarray(x, dtype=_np.float64)
|
|
838
|
+
if len(args) == 1:
|
|
839
|
+
y = list(args[0])
|
|
840
|
+
else:
|
|
841
|
+
y = list(args)
|
|
842
|
+
if isinstance(list(y)[0], int | float):
|
|
843
|
+
y = [list(y)]
|
|
844
|
+
y = _np.asarray(y, dtype=_np.float64)
|
|
845
|
+
base_set = kwargs.pop("baseline", "zero")
|
|
846
|
+
if base_set not in ["zero", "sym", "wiggle", "weighted_wiggle"]:
|
|
847
|
+
raise Warning(f"Invalid baseline: {base_set}. Must be one of 'zero', 'sym', 'wiggle', or 'weighted_wiggle'.")
|
|
848
|
+
if base_set == "zero":
|
|
849
|
+
baseline = _np.zeros(n, dtype=_np.float64)
|
|
850
|
+
elif base_set == "sym":
|
|
851
|
+
baseline = -_np.sum(y, axis=0)/2
|
|
852
|
+
elif base_set == "wiggle": # from matplotlib source
|
|
853
|
+
m = y.shape[0]
|
|
854
|
+
baseline = (y * (m - 0.5 - _np.arange(m)[:, None])).sum(0)
|
|
855
|
+
baseline /= -m
|
|
856
|
+
else: # weighted_wiggle; from matplotlib source
|
|
857
|
+
total = _np.sum(y, 0)
|
|
858
|
+
inv_total = _np.zeros_like(total)
|
|
859
|
+
mask = total > 0
|
|
860
|
+
inv_total[mask] = 1.0 / total[mask]
|
|
861
|
+
increase = _np.hstack((y[:, 0:1], _np.diff(y)))
|
|
862
|
+
below_size = total - _np.cumsum(y, axis=0, dtype=_np.promote_types(y.dtype, _np.float32))
|
|
863
|
+
below_size += 0.5 * y
|
|
864
|
+
move_up = below_size * inv_total
|
|
865
|
+
move_up[:, 0] = 0.5
|
|
866
|
+
center = (move_up - 0.5) * increase
|
|
867
|
+
center = _np.cumsum(center.sum(0))
|
|
868
|
+
baseline = center - 0.5 * total
|
|
869
|
+
overlay = self._get_free_overlay()
|
|
870
|
+
self._overlay_special[overlay] = {"stack plots": "y", "area style": None}
|
|
871
|
+
if base_set != "zero":
|
|
872
|
+
self._plot(x, baseline, overlay=overlay, alpha=0)
|
|
873
|
+
datas = {}
|
|
874
|
+
for kw in ["labels", "colors", "alpha", "facecolor", "fc", "edgecolor", "ec", "linewidth", "lw", "linestyle", "ls", "hatch", "hatch_color", "hatch_linewidth", "hatch_distance"]:
|
|
875
|
+
if kw in kwargs:
|
|
876
|
+
prop = kwargs.pop(kw)
|
|
877
|
+
try:
|
|
878
|
+
prop = list(prop)
|
|
879
|
+
except: pass
|
|
880
|
+
if isinstance(prop, (list, tuple)):
|
|
881
|
+
if len(prop) != len(y):
|
|
882
|
+
raise Warning(f"Length of {kw} does not match number of datasets.")
|
|
883
|
+
else:
|
|
884
|
+
prop = [prop] * len(y)
|
|
885
|
+
datas[kw.removesuffix("s")] = prop
|
|
886
|
+
if "label" in datas:
|
|
887
|
+
self._overlay_special[overlay]["area legend"] = None
|
|
888
|
+
output = []
|
|
889
|
+
for i in range(len(y)):
|
|
890
|
+
kws = {kw: datas[kw][i] for kw in datas}
|
|
891
|
+
kws["_endnotes"] = r"\closedcycle"
|
|
892
|
+
output.append(self._plot(x, y[i], overlay=overlay, settings={"fill": None}, **kws))
|
|
893
|
+
return output
|
|
687
894
|
|
|
688
895
|
def step(self, x, y, *args, **kwargs):
|
|
689
896
|
kws = {"fmt", "alpha", "color", "c", "linestyle", "ls", "linewidth", "lw", "marker", "markersize", "ms", "label", "where"}
|
|
@@ -878,6 +1085,8 @@ class BaseAxes:
|
|
|
878
1085
|
self._axis_options["ytick pos"] = Y_INV[(yt_l, yt_r)]
|
|
879
1086
|
if self._axis_options["xtick pos"] == "both":
|
|
880
1087
|
self._axis_options.pop("xtick pos")
|
|
1088
|
+
if self._axis_options["ytick pos"] == "both":
|
|
1089
|
+
self._axis_options.pop("ytick pos")
|
|
881
1090
|
if "colors" in kwargs:
|
|
882
1091
|
c = self._match_color(kwargs.pop("colors"))
|
|
883
1092
|
self._update_axis_options(prefix + " tick style", {"draw": c})
|
|
@@ -1140,7 +1349,7 @@ class Axes(BaseAxes):
|
|
|
1140
1349
|
self._style = self._fig._style
|
|
1141
1350
|
self._imshow = None
|
|
1142
1351
|
|
|
1143
|
-
self._defcol_counter = 0
|
|
1352
|
+
self._defcol_counter = {0: 0}
|
|
1144
1353
|
self._colorbar = ""
|
|
1145
1354
|
self._cbar_h = False
|
|
1146
1355
|
self._polar = polar
|
|
@@ -1537,9 +1746,11 @@ class Axes(BaseAxes):
|
|
|
1537
1746
|
return self._nrows
|
|
1538
1747
|
def _get_ncols(self):
|
|
1539
1748
|
return self._ncols
|
|
1540
|
-
def _get_defcol(self):
|
|
1541
|
-
self._defcol_counter
|
|
1542
|
-
|
|
1749
|
+
def _get_defcol(self, index = 0):
|
|
1750
|
+
if index not in self._defcol_counter:
|
|
1751
|
+
self._defcol_counter[index] = 0
|
|
1752
|
+
self._defcol_counter[index] += 1
|
|
1753
|
+
return self._defcol_counter[index] - 1
|
|
1543
1754
|
def _show_colorbar(self, cbar, horizontal=False):
|
|
1544
1755
|
self._colorbar = ",\n" + cbar
|
|
1545
1756
|
self._cbar_h = horizontal
|
|
@@ -1550,7 +1761,7 @@ class Axes(BaseAxes):
|
|
|
1550
1761
|
for k,v in self._elements.items():
|
|
1551
1762
|
if k in self._bar_labels and len(self._bar_labels[k]) > 0:
|
|
1552
1763
|
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]
|
|
1764
|
+
self._overlay_special[k].update({"nodes near coords": None})
|
|
1554
1765
|
for e in self._elements[k].copy():
|
|
1555
1766
|
if e not in self._bar_labels[k]:
|
|
1556
1767
|
self.bar_label(e, labels=[])
|
|
@@ -1561,13 +1772,13 @@ class Axes(BaseAxes):
|
|
|
1561
1772
|
"""if isinstance(v, list) and len(v) > 1:
|
|
1562
1773
|
reference = getattr(v[0], "_settings", {}) or {}
|
|
1563
1774
|
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
|
-
|
|
1565
|
-
for f in
|
|
1775
|
+
forbidden = {"draw", "fill", "xbar", "ybar"}
|
|
1776
|
+
for f in forbidden:
|
|
1566
1777
|
common_style.pop(f, None)
|
|
1567
1778
|
for key, val in common_style.items():
|
|
1568
1779
|
for e in v:
|
|
1569
1780
|
e._settings.pop(key, None)
|
|
1570
|
-
self._overlay_special[k]
|
|
1781
|
+
self._overlay_special[k].update({key: val}) """
|
|
1571
1782
|
lines = []
|
|
1572
1783
|
lines2 = []
|
|
1573
1784
|
if self._polar:
|
|
@@ -1577,7 +1788,7 @@ class Axes(BaseAxes):
|
|
|
1577
1788
|
if self._polar and TikzConfig.USE_GROUPPLOTS and not single:
|
|
1578
1789
|
lines.append(f"\\nextgroupplot[alias={self._axis_options['alias']}, width={self._width}, height={self._height}, hide axis]")
|
|
1579
1790
|
for i in self._elements.keys():
|
|
1580
|
-
spec = self._overlay_special
|
|
1791
|
+
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 ""
|
|
1581
1792
|
lines2.append("\\begin{polaraxis}[")
|
|
1582
1793
|
if i == self._get_overlay():
|
|
1583
1794
|
lines2.append(f"{main_ax}{spec}\n]")
|
|
@@ -1593,18 +1804,18 @@ class Axes(BaseAxes):
|
|
|
1593
1804
|
elif not TikzConfig.USE_GROUPPLOTS or (TikzConfig.USE_GROUPPLOTS and single):
|
|
1594
1805
|
lines.append("\\begin{axis}[")
|
|
1595
1806
|
if self._get_overlay() == 0:
|
|
1596
|
-
spec = self._overlay_special
|
|
1807
|
+
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 ""
|
|
1597
1808
|
if self._secondary_y is not None or self._colorbar is not None:
|
|
1598
1809
|
lines.append(f"{main_ax}{spec}alias={alias}\n]")
|
|
1599
1810
|
else:
|
|
1600
1811
|
lines.append(f"{main_ax}{spec}\n]")
|
|
1601
1812
|
lines.append(contents[0])
|
|
1602
1813
|
else:
|
|
1603
|
-
spec = self._overlay_special
|
|
1814
|
+
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 ""
|
|
1604
1815
|
lines.append(f"{aux_ax}{spec}alias={alias}\n]")
|
|
1605
1816
|
lines.append(contents[0])
|
|
1606
1817
|
for i in self._elements.keys():
|
|
1607
|
-
spec = self._overlay_special
|
|
1818
|
+
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 ""
|
|
1608
1819
|
if i == 0: continue
|
|
1609
1820
|
lines2.append("\\begin{axis}[")
|
|
1610
1821
|
if i == self._get_overlay():
|
|
@@ -1627,7 +1838,7 @@ class Axes(BaseAxes):
|
|
|
1627
1838
|
contents2 = self._secondary_y._content_tex(filename)
|
|
1628
1839
|
for i in self._secondary_y._elements.keys():
|
|
1629
1840
|
lines2.append("\\begin{axis}[")
|
|
1630
|
-
spec = self._secondary_y._overlay_special
|
|
1841
|
+
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 ""
|
|
1631
1842
|
if i == sorted(self._secondary_y._elements.keys())[-1]:
|
|
1632
1843
|
lines2.append(f"{main_ax2}{spec}\n]")
|
|
1633
1844
|
else:
|
|
@@ -1715,8 +1926,8 @@ class Secondary(BaseAxes):
|
|
|
1715
1926
|
def _padding(self):
|
|
1716
1927
|
return TikzConfig.SEC_Y_PADDING + TikzConfig.YTICK_PADDING * self._yticks + TikzConfig.SEC_Y_LABEL_PADDING * ("ylabel" in self._axis_options)
|
|
1717
1928
|
|
|
1718
|
-
def _get_defcol(self):
|
|
1719
|
-
return self._primary._get_defcol()
|
|
1929
|
+
def _get_defcol(self, index = 0):
|
|
1930
|
+
return self._primary._get_defcol(index)
|
|
1720
1931
|
|
|
1721
1932
|
def _get_index(self):
|
|
1722
1933
|
return self._primary._get_index()
|