tesorotools-python 0.0.56__tar.gz → 0.1.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.
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/PKG-INFO +4 -1
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/pyproject.toml +7 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/__init__.py +16 -4
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/_common.py +108 -4
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/bar_line.py +18 -1
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/barh_plot.py +46 -16
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/box_plot.py +16 -1
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/line_plot.py +23 -1
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/matrix.py +19 -9
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/plotly_backend.py +5 -3
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/shock_plot.py +13 -1
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/stacked.py +47 -16
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/type_curve.py +21 -9
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/vector_plot.py +20 -9
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/waterfall.py +13 -1
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/template.docx +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/database/shared.py +6 -6
- tesorotools_python-0.1.1/src/tesorotools/latex/__init__.py +13 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/cli.py +119 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/scaffold.py +185 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/article/main.tex.tmpl +33 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/article/sections/01-introduccion.tex +18 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/article/sections/02-analisis.tex +51 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/article/sections/03-conclusiones.tex +10 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/beamer/main.tex.tmpl +47 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/beamer/slides/01-contexto.tex +28 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/beamer/slides/02-analisis.tex +39 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/beamer/slides/03-conclusiones.tex +13 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/beamer/style/beamerthemeTesoro.sty +123 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/book/appendices/a-fuentes.tex +10 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/book/chapters/01-introduccion.tex +25 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/book/chapters/02-analisis.tex +49 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/book/frontmatter/resumen.tex +10 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/book/main.tex.tmpl +41 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/README.md.tmpl +50 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/dot-gitignore +21 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/dot-vscode/settings.json +20 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/fonts/README.md +15 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/latexmkrc +9 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/refs.bib +17 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/style/tesoro.sty +207 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/style/tesorobrand.sty.tmpl +10 -0
- tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/style/tesorolocal.sty.tmpl +10 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/manifest.py +7 -7
- tesorotools_python-0.1.1/src/tesorotools/palette.py +59 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/providers/fred.py +3 -4
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/providers/imf_irfcl.py +2 -1
- tesorotools_python-0.1.1/src/tesorotools/render/content/__init__.py +0 -0
- tesorotools_python-0.1.1/src/tesorotools/scaffold.py +127 -0
- tesorotools_python-0.1.1/src/tesorotools/web/__init__.py +112 -0
- tesorotools_python-0.1.1/src/tesorotools/web/app.py +221 -0
- tesorotools_python-0.1.1/src/tesorotools/web/brand.py +355 -0
- tesorotools_python-0.1.1/src/tesorotools/web/cli.py +92 -0
- tesorotools_python-0.1.1/src/tesorotools/web/components.py +210 -0
- tesorotools_python-0.1.1/src/tesorotools/web/formatting.py +45 -0
- tesorotools_python-0.1.1/src/tesorotools/web/launcher.py +42 -0
- tesorotools_python-0.1.1/src/tesorotools/web/palette.py +16 -0
- tesorotools_python-0.1.1/src/tesorotools/web/plotting.py +117 -0
- tesorotools_python-0.1.1/src/tesorotools/web/scaffold.py +95 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/Dockerfile.tmpl +34 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/README.md.tmpl +43 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/dot-dockerignore.tmpl +6 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/dot-gitignore.tmpl +6 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/dot-streamlit/config.toml.tmpl +14 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/pyproject.toml.tmpl +35 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/src/$package/__init__.py.tmpl +1 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/src/$package/cli.py.tmpl +11 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/src/$package/main.py.tmpl +30 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/src/$package/sections.py.tmpl +82 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/tests/__init__.py.tmpl +0 -0
- tesorotools_python-0.1.1/src/tesorotools/web/templates/tests/test_app.py.tmpl +22 -0
- tesorotools_python-0.1.1/src/tesorotools/web/theme.py +295 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/.gitignore +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/_build_context.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/_registry.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/compact.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/intraday_plot.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/plots.yaml +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/driver.py +0 -0
- /tesorotools_python-0.0.56/src/tesorotools/offsets/__init__.py → /tesorotools_python-0.1.1/src/tesorotools/latex/templates/common/figures/dot-gitkeep +0 -0
- {tesorotools_python-0.0.56/src/tesorotools/pipeline → tesorotools_python-0.1.1/src/tesorotools/offsets}/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/orchestration.py +0 -0
- {tesorotools_python-0.0.56/src/tesorotools/render/content → tesorotools_python-0.1.1/src/tesorotools/pipeline}/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/providers/lseg.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/content/table.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/document.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/utils/template.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tesorotools-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Requires-Python: >=3.13
|
|
5
5
|
Requires-Dist: babel>=2.17
|
|
6
6
|
Requires-Dist: matplotlib>=3.10
|
|
@@ -25,3 +25,6 @@ Provides-Extra: interactive-plots
|
|
|
25
25
|
Requires-Dist: plotly>=5.0; extra == 'interactive-plots'
|
|
26
26
|
Provides-Extra: lseg
|
|
27
27
|
Requires-Dist: lseg-data>=2.1; extra == 'lseg'
|
|
28
|
+
Provides-Extra: web
|
|
29
|
+
Requires-Dist: plotly>=5.24; extra == 'web'
|
|
30
|
+
Requires-Dist: streamlit>=1.62; extra == 'web'
|
|
@@ -31,6 +31,13 @@ fred = ["requests>=2.31"]
|
|
|
31
31
|
imf = ["requests>=2.31", "truststore>=0.10"]
|
|
32
32
|
lseg = ["lseg-data>=2.1"]
|
|
33
33
|
interactive_plots = ["plotly>=5.0"]
|
|
34
|
+
# Streamlit dashboards scaffolding (tesorotools.web). The CSS targets
|
|
35
|
+
# Streamlit's DOM (data-testid), so keep the floor at a tested version.
|
|
36
|
+
web = ["streamlit>=1.62", "plotly>=5.24"]
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
tesorotools-web = "tesorotools.web.cli:main"
|
|
40
|
+
tesorotools-latex = "tesorotools.latex.cli:main"
|
|
34
41
|
|
|
35
42
|
[dependency-groups]
|
|
36
43
|
dev = [
|
|
@@ -12,9 +12,13 @@ Each class follows the same shape:
|
|
|
12
12
|
``data_path`` (``.feather`` on disk), never both.
|
|
13
13
|
* ``from_yaml(loader, node)`` -- builds the same instance
|
|
14
14
|
from a ``!{name}`` YAML tag.
|
|
15
|
-
* ``
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
* ``build()`` -- renders the chart in memory and returns
|
|
16
|
+
``(Figure, Axes)``; ``save(fig, path=..., dpi=...)``
|
|
17
|
+
writes it; ``plot()`` does both and closes the figure.
|
|
18
|
+
Every artist exposes the three (see :class:`Artist`).
|
|
19
|
+
* ``to_plotly()`` -- the same chart as an interactive
|
|
20
|
+
Plotly figure (all but ``IntradayPlot``);
|
|
21
|
+
:func:`plotly_unsupported` lists the keys it drops.
|
|
18
22
|
|
|
19
23
|
Shared layout, annotation and styling helpers (plus the
|
|
20
24
|
``Format`` and ``Legend`` config holders) live in
|
|
@@ -25,7 +29,13 @@ live under :mod:`tesorotools.render`; this package is
|
|
|
25
29
|
strictly image output.
|
|
26
30
|
"""
|
|
27
31
|
|
|
28
|
-
from tesorotools.artists._common import
|
|
32
|
+
from tesorotools.artists._common import (
|
|
33
|
+
Artist,
|
|
34
|
+
FigureLifecycle,
|
|
35
|
+
Format,
|
|
36
|
+
Legend,
|
|
37
|
+
plotly_unsupported,
|
|
38
|
+
)
|
|
29
39
|
from tesorotools.artists.bar_line import BarLinePlot
|
|
30
40
|
from tesorotools.artists.barh_plot import GroupedBarChart, HorizontalBarChart
|
|
31
41
|
from tesorotools.artists.box_plot import BoxPlot
|
|
@@ -42,6 +52,7 @@ __all__ = [
|
|
|
42
52
|
"Artist",
|
|
43
53
|
"BarLinePlot",
|
|
44
54
|
"BoxPlot",
|
|
55
|
+
"FigureLifecycle",
|
|
45
56
|
"Format",
|
|
46
57
|
"GroupedBarChart",
|
|
47
58
|
"HorizontalBarChart",
|
|
@@ -55,4 +66,5 @@ __all__ = [
|
|
|
55
66
|
"TypeCurve",
|
|
56
67
|
"VectorPlot",
|
|
57
68
|
"Waterfall",
|
|
69
|
+
"plotly_unsupported",
|
|
58
70
|
]
|
|
@@ -29,7 +29,11 @@ from typing import Any, Literal, Protocol, Self, cast, runtime_checkable
|
|
|
29
29
|
import matplotlib.pyplot as plt
|
|
30
30
|
import pandas as pd
|
|
31
31
|
from matplotlib.axes import Axes
|
|
32
|
-
from matplotlib.dates import
|
|
32
|
+
from matplotlib.dates import (
|
|
33
|
+
AutoDateLocator,
|
|
34
|
+
ConciseDateFormatter,
|
|
35
|
+
YearLocator,
|
|
36
|
+
)
|
|
33
37
|
from matplotlib.dates import (
|
|
34
38
|
date2num, # type: ignore[reportUnknownVariableType]
|
|
35
39
|
)
|
|
@@ -126,6 +130,70 @@ def dynamic_dpi(fig: Figure) -> int:
|
|
|
126
130
|
return int(round(min(max_dpi, max(min_dpi, target_px / long_in))))
|
|
127
131
|
|
|
128
132
|
|
|
133
|
+
def plotly_unsupported(artist: object) -> frozenset[str]:
|
|
134
|
+
"""Configuration keys *artist* accepts but ``to_plotly()`` drops.
|
|
135
|
+
|
|
136
|
+
*artist* is an artist class or instance. The keys change the PNG
|
|
137
|
+
but not the interactive figure; nested ones are dotted
|
|
138
|
+
(``"legend.ncol"``, ``"fmt.signed"``). A UI built on top can tell
|
|
139
|
+
the user why moving such a control leaves the figure as it is.
|
|
140
|
+
Needs no ``plotly``: it reads the class attribute
|
|
141
|
+
``PLOTLY_UNSUPPORTED``, which the test suite checks against real
|
|
142
|
+
renders.
|
|
143
|
+
"""
|
|
144
|
+
cls = artist if isinstance(artist, type) else type(artist)
|
|
145
|
+
if not hasattr(cls, "to_plotly"):
|
|
146
|
+
raise TypeError(f"{cls.__name__} has no to_plotly()")
|
|
147
|
+
declared: frozenset[str] | None = getattr(cls, "PLOTLY_UNSUPPORTED", None)
|
|
148
|
+
if declared is None:
|
|
149
|
+
raise TypeError(f"{cls.__name__} does not declare PLOTLY_UNSUPPORTED")
|
|
150
|
+
return frozenset(declared)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class FigureLifecycle:
|
|
154
|
+
"""``save()`` and ``plot()`` on top of a subclass's ``build()``.
|
|
155
|
+
|
|
156
|
+
Gives an artist the same three-step life cycle as the rest:
|
|
157
|
+
``build()`` returns the figure in memory, ``save()`` writes it
|
|
158
|
+
and ``plot()`` does both and closes the figure. A subclass only
|
|
159
|
+
implements ``build()``.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
out_path: Path
|
|
163
|
+
|
|
164
|
+
def build(self) -> tuple[Figure, Axes]:
|
|
165
|
+
raise NotImplementedError
|
|
166
|
+
|
|
167
|
+
def save(
|
|
168
|
+
self,
|
|
169
|
+
fig: Figure,
|
|
170
|
+
*,
|
|
171
|
+
path: Path | None = None,
|
|
172
|
+
dpi: int | None = None,
|
|
173
|
+
) -> Path:
|
|
174
|
+
"""Persist *fig* as a PNG and return the path written.
|
|
175
|
+
|
|
176
|
+
Defaults to ``self.out_path``; pass ``path`` to redirect.
|
|
177
|
+
``dpi`` overrides the save resolution; when omitted it is
|
|
178
|
+
derived from the figure size via :func:`dynamic_dpi`.
|
|
179
|
+
"""
|
|
180
|
+
target: Path = path if path is not None else self.out_path
|
|
181
|
+
save_dpi: int = dpi if dpi is not None else dynamic_dpi(fig)
|
|
182
|
+
fig.savefig( # type: ignore[reportUnknownMemberType]
|
|
183
|
+
target, dpi=save_dpi
|
|
184
|
+
)
|
|
185
|
+
return target
|
|
186
|
+
|
|
187
|
+
def plot(self) -> Axes:
|
|
188
|
+
"""Build the chart, persist it to ``self.out_path`` and close it."""
|
|
189
|
+
fig, ax = self.build()
|
|
190
|
+
try:
|
|
191
|
+
self.save(fig)
|
|
192
|
+
finally:
|
|
193
|
+
plt.close(fig)
|
|
194
|
+
return ax
|
|
195
|
+
|
|
196
|
+
|
|
129
197
|
# ----------------------------------------------------------------------
|
|
130
198
|
# Structural contract for image artists
|
|
131
199
|
# ----------------------------------------------------------------------
|
|
@@ -137,13 +205,21 @@ class Artist(Protocol):
|
|
|
137
205
|
|
|
138
206
|
The built-in artists and any third-party artist
|
|
139
207
|
registered via :func:`tesorotools.register_artist`
|
|
140
|
-
expose the same
|
|
208
|
+
expose the same surface:
|
|
141
209
|
|
|
142
210
|
* ``out_path: Path`` -- the ``.png`` destination.
|
|
143
211
|
* ``from_yaml(loader, node) -> Self`` -- the YAML
|
|
144
212
|
constructor bound to the ``!{name}`` tag.
|
|
145
|
-
* ``
|
|
146
|
-
|
|
213
|
+
* ``build() -> (Figure, Axes)`` -- render the chart in
|
|
214
|
+
memory, without touching the disk.
|
|
215
|
+
* ``save(fig, *, path=None, dpi=None) -> Path`` -- write
|
|
216
|
+
a built figure (to ``out_path`` unless ``path`` says
|
|
217
|
+
otherwise).
|
|
218
|
+
* ``plot() -> Axes`` -- ``build()`` + ``save()``, close
|
|
219
|
+
the figure and return the axes for inspection.
|
|
220
|
+
|
|
221
|
+
:class:`FigureLifecycle` provides ``save()`` and ``plot()``
|
|
222
|
+
for an artist that only implements ``build()``.
|
|
147
223
|
|
|
148
224
|
The Protocol is ``runtime_checkable`` so consumers can
|
|
149
225
|
use ``isinstance(obj, Artist)`` to validate a candidate
|
|
@@ -156,6 +232,16 @@ class Artist(Protocol):
|
|
|
156
232
|
@classmethod
|
|
157
233
|
def from_yaml(cls, loader: Any, node: MappingNode) -> Self: ...
|
|
158
234
|
|
|
235
|
+
def build(self) -> tuple[Figure, Axes]: ...
|
|
236
|
+
|
|
237
|
+
def save(
|
|
238
|
+
self,
|
|
239
|
+
fig: Figure,
|
|
240
|
+
*,
|
|
241
|
+
path: Path | None = None,
|
|
242
|
+
dpi: int | None = None,
|
|
243
|
+
) -> Path: ...
|
|
244
|
+
|
|
159
245
|
def plot(self) -> Axes: ...
|
|
160
246
|
|
|
161
247
|
|
|
@@ -580,6 +666,12 @@ def date_span_ticks(
|
|
|
580
666
|
10 % of the span is dropped so the two labels do not collide. Without
|
|
581
667
|
it the plain in-span candidates are returned (falling back to the two
|
|
582
668
|
endpoints when the locator offers nothing inside).
|
|
669
|
+
|
|
670
|
+
When the locator steps by several years, the pinned grid is anchored to
|
|
671
|
+
the **start year** instead of to multiples of the step: a 2-year step
|
|
672
|
+
over 2023-2027 gives 2023, 2025, 2027 rather than the pinned 2023 plus
|
|
673
|
+
the calendar 2024, 2026, whose uneven gaps (and missing odd years) read
|
|
674
|
+
as if the axis skipped a year.
|
|
583
675
|
"""
|
|
584
676
|
lo: float = float(cast(Any, date2num(data_min)))
|
|
585
677
|
hi: float = float(cast(Any, date2num(data_max)))
|
|
@@ -589,6 +681,18 @@ def date_span_ticks(
|
|
|
589
681
|
)
|
|
590
682
|
if not pin_start:
|
|
591
683
|
return [t for t in candidates if lo <= t <= hi] or [lo, hi]
|
|
684
|
+
chosen: Any = locator.get_locator( # type: ignore[reportUnknownMemberType]
|
|
685
|
+
data_min, data_max
|
|
686
|
+
)
|
|
687
|
+
if isinstance(chosen, YearLocator):
|
|
688
|
+
step = int(cast(Any, chosen.base).step)
|
|
689
|
+
if step > 1:
|
|
690
|
+
first_year: int = num2date(lo).year
|
|
691
|
+
last_year: int = num2date(hi).year
|
|
692
|
+
candidates = [
|
|
693
|
+
float(cast(Any, date2num(pd.Timestamp(year=y, month=1, day=1))))
|
|
694
|
+
for y in range(first_year, last_year + 1, step)
|
|
695
|
+
]
|
|
592
696
|
min_gap: float = (hi - lo) * 0.10
|
|
593
697
|
interior: list[float] = [
|
|
594
698
|
t for t in candidates if lo < t <= hi and t - lo >= min_gap
|
|
@@ -28,7 +28,7 @@ from __future__ import annotations
|
|
|
28
28
|
|
|
29
29
|
import locale
|
|
30
30
|
from pathlib import Path
|
|
31
|
-
from typing import Any, Self
|
|
31
|
+
from typing import Any, ClassVar, Self
|
|
32
32
|
|
|
33
33
|
import matplotlib.pyplot as plt
|
|
34
34
|
import numpy as np
|
|
@@ -89,6 +89,23 @@ class BarLinePlot:
|
|
|
89
89
|
(``None`` labels every slot).
|
|
90
90
|
"""
|
|
91
91
|
|
|
92
|
+
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
93
|
+
#: (see :func:`plotly_unsupported`); checked against real renders
|
|
94
|
+
#: by ``tests/test_plotly_support.py``.
|
|
95
|
+
PLOTLY_UNSUPPORTED: ClassVar[frozenset[str]] = frozenset(
|
|
96
|
+
{
|
|
97
|
+
"bar_width",
|
|
98
|
+
"compact",
|
|
99
|
+
"figsize",
|
|
100
|
+
"fmt.decimals",
|
|
101
|
+
"font_pt",
|
|
102
|
+
"legend.ncol",
|
|
103
|
+
"max_xticks",
|
|
104
|
+
"plot_size",
|
|
105
|
+
"x_rotation",
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
92
109
|
def __init__(
|
|
93
110
|
self,
|
|
94
111
|
out_path: Path,
|
|
@@ -18,7 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
from enum import Enum
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Any, Self
|
|
21
|
+
from typing import Any, ClassVar, Self
|
|
22
22
|
|
|
23
23
|
import matplotlib.patheffects as pe
|
|
24
24
|
import matplotlib.pyplot as plt
|
|
@@ -32,6 +32,7 @@ from matplotlib.ticker import FuncFormatter
|
|
|
32
32
|
from yaml.nodes import MappingNode
|
|
33
33
|
|
|
34
34
|
from tesorotools.artists._common import (
|
|
35
|
+
FigureLifecycle,
|
|
35
36
|
AX_CONFIG,
|
|
36
37
|
Format,
|
|
37
38
|
Legend,
|
|
@@ -39,7 +40,6 @@ from tesorotools.artists._common import (
|
|
|
39
40
|
compact_barh_figsize,
|
|
40
41
|
compact_figure_kwargs,
|
|
41
42
|
compact_font_pt,
|
|
42
|
-
dynamic_dpi,
|
|
43
43
|
legend_layout,
|
|
44
44
|
place_legend,
|
|
45
45
|
resolve_data,
|
|
@@ -447,7 +447,7 @@ def _place_in_or_out_labels(
|
|
|
447
447
|
return outside
|
|
448
448
|
|
|
449
449
|
|
|
450
|
-
class HorizontalBarChart:
|
|
450
|
+
class HorizontalBarChart(FigureLifecycle):
|
|
451
451
|
"""Horizontal bar chart artist.
|
|
452
452
|
|
|
453
453
|
Parameters
|
|
@@ -507,6 +507,19 @@ class HorizontalBarChart:
|
|
|
507
507
|
outside; pragmatic but mixes the two looks).
|
|
508
508
|
"""
|
|
509
509
|
|
|
510
|
+
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
511
|
+
#: (see :func:`plotly_unsupported`); checked against real renders
|
|
512
|
+
#: by ``tests/test_plotly_support.py``.
|
|
513
|
+
PLOTLY_UNSUPPORTED: ClassVar[frozenset[str]] = frozenset(
|
|
514
|
+
{
|
|
515
|
+
"color_value_labels",
|
|
516
|
+
"compact",
|
|
517
|
+
"figsize",
|
|
518
|
+
"font_pt",
|
|
519
|
+
"value_label_position",
|
|
520
|
+
}
|
|
521
|
+
)
|
|
522
|
+
|
|
510
523
|
def __init__(
|
|
511
524
|
self,
|
|
512
525
|
out_path: Path,
|
|
@@ -641,7 +654,11 @@ class HorizontalBarChart:
|
|
|
641
654
|
frame = frame.sort_values(by=_Col.VALUE.value)
|
|
642
655
|
return frame
|
|
643
656
|
|
|
644
|
-
def
|
|
657
|
+
def build(self) -> tuple[Figure, Axes]:
|
|
658
|
+
"""Render the chart in memory; :meth:`save` writes it.
|
|
659
|
+
|
|
660
|
+
:meth:`plot` (from :class:`FigureLifecycle`) does both.
|
|
661
|
+
"""
|
|
645
662
|
"""Render the chart and persist it to ``self.out_path``."""
|
|
646
663
|
frame = self._build_frame()
|
|
647
664
|
|
|
@@ -689,14 +706,10 @@ class HorizontalBarChart:
|
|
|
689
706
|
axis="both", labelsize=font_pt
|
|
690
707
|
)
|
|
691
708
|
|
|
692
|
-
fig
|
|
693
|
-
self.out_path, dpi=dynamic_dpi(fig)
|
|
694
|
-
)
|
|
695
|
-
plt.close(fig)
|
|
696
|
-
return ax
|
|
709
|
+
return fig, ax
|
|
697
710
|
|
|
698
711
|
|
|
699
|
-
class GroupedBarChart:
|
|
712
|
+
class GroupedBarChart(FigureLifecycle):
|
|
700
713
|
"""Grouped (clustered) horizontal bar chart artist.
|
|
701
714
|
|
|
702
715
|
Compares two or more series side-by-side within each
|
|
@@ -764,6 +777,23 @@ class GroupedBarChart:
|
|
|
764
777
|
see :class:`HorizontalBarChart`.
|
|
765
778
|
"""
|
|
766
779
|
|
|
780
|
+
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
781
|
+
#: (see :func:`plotly_unsupported`); checked against real renders
|
|
782
|
+
#: by ``tests/test_plotly_support.py``.
|
|
783
|
+
PLOTLY_UNSUPPORTED: ClassVar[frozenset[str]] = frozenset(
|
|
784
|
+
{
|
|
785
|
+
"annotate",
|
|
786
|
+
"color_value_labels",
|
|
787
|
+
"compact",
|
|
788
|
+
"figsize",
|
|
789
|
+
"font_pt",
|
|
790
|
+
"group_width",
|
|
791
|
+
"legend.ncol",
|
|
792
|
+
"plot_size",
|
|
793
|
+
"value_label_position",
|
|
794
|
+
}
|
|
795
|
+
)
|
|
796
|
+
|
|
767
797
|
def __init__(
|
|
768
798
|
self,
|
|
769
799
|
out_path: Path,
|
|
@@ -867,7 +897,11 @@ class GroupedBarChart:
|
|
|
867
897
|
|
|
868
898
|
return grouped_bar_to_figure(self)
|
|
869
899
|
|
|
870
|
-
def
|
|
900
|
+
def build(self) -> tuple[Figure, Axes]:
|
|
901
|
+
"""Render the chart in memory; :meth:`save` writes it.
|
|
902
|
+
|
|
903
|
+
:meth:`plot` (from :class:`FigureLifecycle`) does both.
|
|
904
|
+
"""
|
|
871
905
|
"""Render the chart and persist it to ``self.out_path``."""
|
|
872
906
|
cat_ids = list(self.categories.keys())
|
|
873
907
|
ser_ids = list(self.series.keys())
|
|
@@ -941,8 +975,4 @@ class GroupedBarChart:
|
|
|
941
975
|
if self.plot_size is not None and not self.compact:
|
|
942
976
|
adjust_figure_for_plot_size(fig, ax, self.plot_size)
|
|
943
977
|
|
|
944
|
-
fig
|
|
945
|
-
self.out_path, dpi=dynamic_dpi(fig)
|
|
946
|
-
)
|
|
947
|
-
plt.close(fig)
|
|
948
|
-
return ax
|
|
978
|
+
return fig, ax
|
|
@@ -32,7 +32,7 @@ The last-value marker is a circle by default and fully configurable
|
|
|
32
32
|
from __future__ import annotations
|
|
33
33
|
|
|
34
34
|
from pathlib import Path
|
|
35
|
-
from typing import Any, Self
|
|
35
|
+
from typing import Any, ClassVar, Self
|
|
36
36
|
|
|
37
37
|
import matplotlib.pyplot as plt
|
|
38
38
|
import numpy as np
|
|
@@ -129,6 +129,21 @@ class BoxPlot:
|
|
|
129
129
|
* ``legend_labels`` -- override the four proxy labels.
|
|
130
130
|
"""
|
|
131
131
|
|
|
132
|
+
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
133
|
+
#: (see :func:`plotly_unsupported`); checked against real renders
|
|
134
|
+
#: by ``tests/test_plotly_support.py``.
|
|
135
|
+
PLOTLY_UNSUPPORTED: ClassVar[frozenset[str]] = frozenset(
|
|
136
|
+
{
|
|
137
|
+
"compact",
|
|
138
|
+
"figsize",
|
|
139
|
+
"fmt.decimals",
|
|
140
|
+
"font_pt",
|
|
141
|
+
"legend.ncol",
|
|
142
|
+
"plot_size",
|
|
143
|
+
"x_rotation",
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
|
|
132
147
|
def __init__(
|
|
133
148
|
self,
|
|
134
149
|
out_path: Path,
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import datetime
|
|
13
13
|
import locale
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Any, Self
|
|
15
|
+
from typing import Any, ClassVar, Self
|
|
16
16
|
|
|
17
17
|
import matplotlib.pyplot as plt
|
|
18
18
|
import pandas as pd
|
|
@@ -166,6 +166,28 @@ class LinePlot:
|
|
|
166
166
|
``plot()`` is the thin convenience that does both.
|
|
167
167
|
"""
|
|
168
168
|
|
|
169
|
+
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
170
|
+
#: (see :func:`plotly_unsupported`); checked against real renders
|
|
171
|
+
#: by ``tests/test_plotly_support.py``.
|
|
172
|
+
PLOTLY_UNSUPPORTED: ClassVar[frozenset[str]] = frozenset(
|
|
173
|
+
{
|
|
174
|
+
"adapt_height",
|
|
175
|
+
"axis_decimals",
|
|
176
|
+
"compact",
|
|
177
|
+
"date_density",
|
|
178
|
+
"date_rotation",
|
|
179
|
+
"date_show_start",
|
|
180
|
+
"figsize",
|
|
181
|
+
"font_pt",
|
|
182
|
+
"legend.ncol",
|
|
183
|
+
"means_window",
|
|
184
|
+
"plot_size",
|
|
185
|
+
"ref_lines",
|
|
186
|
+
"show_means",
|
|
187
|
+
"vlines",
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
169
191
|
def __init__(
|
|
170
192
|
self,
|
|
171
193
|
out_path: Path,
|
|
@@ -27,7 +27,7 @@ Styling constants come from ``PLOT_CONFIG['matrix']``.
|
|
|
27
27
|
from __future__ import annotations
|
|
28
28
|
|
|
29
29
|
from pathlib import Path
|
|
30
|
-
from typing import Any, Self
|
|
30
|
+
from typing import Any, ClassVar, Self
|
|
31
31
|
|
|
32
32
|
import matplotlib as mpl
|
|
33
33
|
import matplotlib.pyplot as plt
|
|
@@ -40,9 +40,9 @@ from matplotlib.patches import Rectangle
|
|
|
40
40
|
from yaml.nodes import MappingNode
|
|
41
41
|
|
|
42
42
|
from tesorotools.artists._common import (
|
|
43
|
+
FigureLifecycle,
|
|
43
44
|
FIG_CONFIG,
|
|
44
45
|
Format,
|
|
45
|
-
dynamic_dpi,
|
|
46
46
|
resolve_data,
|
|
47
47
|
)
|
|
48
48
|
from tesorotools.utils.matplotlib import PLOT_CONFIG, format_annotation
|
|
@@ -66,7 +66,7 @@ def _text_color(rgba: tuple[float, float, float, float]) -> str:
|
|
|
66
66
|
return MATRIX_CONFIG["dark_text"]
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
class MatrixChart:
|
|
69
|
+
class MatrixChart(FigureLifecycle):
|
|
70
70
|
"""Annotated heatmap-table artist (any ``n_rows x n_cols``).
|
|
71
71
|
|
|
72
72
|
Parameters
|
|
@@ -102,6 +102,16 @@ class MatrixChart:
|
|
|
102
102
|
cells read as tiles.
|
|
103
103
|
"""
|
|
104
104
|
|
|
105
|
+
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
106
|
+
#: (see :func:`plotly_unsupported`); checked against real renders
|
|
107
|
+
#: by ``tests/test_plotly_support.py``.
|
|
108
|
+
PLOTLY_UNSUPPORTED: ClassVar[frozenset[str]] = frozenset(
|
|
109
|
+
{
|
|
110
|
+
"cmap",
|
|
111
|
+
"figsize",
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
|
|
105
115
|
def __init__(
|
|
106
116
|
self,
|
|
107
117
|
out_path: Path,
|
|
@@ -199,7 +209,11 @@ class MatrixChart:
|
|
|
199
209
|
|
|
200
210
|
return matrix_to_figure(self)
|
|
201
211
|
|
|
202
|
-
def
|
|
212
|
+
def build(self) -> tuple[Figure, Axes]:
|
|
213
|
+
"""Render the chart in memory; :meth:`save` writes it.
|
|
214
|
+
|
|
215
|
+
:meth:`plot` (from :class:`FigureLifecycle`) does both.
|
|
216
|
+
"""
|
|
203
217
|
"""Render the chart and persist it to ``self.out_path``."""
|
|
204
218
|
row_ids = list(self.rows)
|
|
205
219
|
col_ids = list(self.cols)
|
|
@@ -260,11 +274,7 @@ class MatrixChart:
|
|
|
260
274
|
)
|
|
261
275
|
|
|
262
276
|
self._style_axes(ax, row_ids, col_ids)
|
|
263
|
-
fig
|
|
264
|
-
self.out_path, dpi=dynamic_dpi(fig)
|
|
265
|
-
)
|
|
266
|
-
plt.close(fig)
|
|
267
|
-
return ax
|
|
277
|
+
return fig, ax
|
|
268
278
|
|
|
269
279
|
def _annotate_cell(
|
|
270
280
|
self,
|
{tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/plotly_backend.py
RENAMED
|
@@ -35,9 +35,11 @@ backends, per the dual-backend research):
|
|
|
35
35
|
``adapt_height``) do nothing here.
|
|
36
36
|
* Beyond those, a few per-artist knobs have no stable Plotly equivalent
|
|
37
37
|
and a few others are simply not translated yet. Both kinds are
|
|
38
|
-
accepted in silence
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
accepted in silence; each artist lists them in ``PLOTLY_UNSUPPORTED``
|
|
39
|
+
(read with :func:`tesorotools.artists.plotly_unsupported`), which
|
|
40
|
+
``tests/test_plotly_support.py`` checks against real renders. When
|
|
41
|
+
translating a key, drop it from there and from the table in
|
|
42
|
+
``docs/plotly-backend.md``.
|
|
41
43
|
"""
|
|
42
44
|
|
|
43
45
|
from __future__ import annotations
|
{tesorotools_python-0.0.56 → tesorotools_python-0.1.1}/src/tesorotools/artists/shock_plot.py
RENAMED
|
@@ -30,7 +30,7 @@ from __future__ import annotations
|
|
|
30
30
|
|
|
31
31
|
from dataclasses import dataclass
|
|
32
32
|
from pathlib import Path
|
|
33
|
-
from typing import Any, Self, cast
|
|
33
|
+
from typing import Any, ClassVar, Self, cast
|
|
34
34
|
|
|
35
35
|
import matplotlib.pyplot as plt
|
|
36
36
|
import pandas as pd
|
|
@@ -124,6 +124,18 @@ class ShockChart:
|
|
|
124
124
|
value instead of each window's own anchor; must be a shock label.
|
|
125
125
|
"""
|
|
126
126
|
|
|
127
|
+
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
128
|
+
#: (see :func:`plotly_unsupported`); checked against real renders
|
|
129
|
+
#: by ``tests/test_plotly_support.py``.
|
|
130
|
+
PLOTLY_UNSUPPORTED: ClassVar[frozenset[str]] = frozenset(
|
|
131
|
+
{
|
|
132
|
+
"compact",
|
|
133
|
+
"figsize",
|
|
134
|
+
"fmt.decimals",
|
|
135
|
+
"legend.ncol",
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
127
139
|
def __init__(
|
|
128
140
|
self,
|
|
129
141
|
out_path: Path,
|