tesorotools-python 0.1.2__tar.gz → 0.1.4__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.1.2 → tesorotools_python-0.1.4}/PKG-INFO +1 -1
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/pyproject.toml +1 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/__init__.py +6 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/__init__.py +4 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/_common.py +62 -2
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/bar_line.py +147 -14
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/box_plot.py +2 -1
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/intraday_plot.py +2 -1
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/line_plot.py +4 -1
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/plotly_backend.py +171 -6
- tesorotools_python-0.1.4/src/tesorotools/artists/sankey.py +436 -0
- tesorotools_python-0.1.4/src/tesorotools/artists/scatter.py +469 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/shock_plot.py +2 -1
- tesorotools_python-0.1.4/src/tesorotools/artists/tikz_backend.py +172 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/waterfall.py +2 -1
- tesorotools_python-0.1.4/src/tesorotools/assets/nota_template.dotx +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/plots.yaml +40 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/style/tesoro.sty +3 -0
- tesorotools_python-0.1.4/src/tesorotools/note/__init__.py +40 -0
- tesorotools_python-0.1.4/src/tesorotools/note/builder.py +749 -0
- tesorotools_python-0.1.4/src/tesorotools/note/cli.py +168 -0
- tesorotools_python-0.1.4/src/tesorotools/note/inline.py +129 -0
- tesorotools_python-0.1.4/src/tesorotools/note/lint.py +291 -0
- tesorotools_python-0.1.4/src/tesorotools/note/markdown.py +613 -0
- tesorotools_python-0.1.4/src/tesorotools/note/skeleton.md +75 -0
- tesorotools_python-0.1.4/src/tesorotools/note/skill/SKILL.md +171 -0
- tesorotools_python-0.1.4/src/tesorotools/note/skill/reglas.md +120 -0
- tesorotools_python-0.1.4/src/tesorotools/note/template.py +120 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/globals.py +4 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/app.py +11 -2
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/brand.py +10 -1
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/components.py +32 -12
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/theme.py +29 -6
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/.gitignore +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/_build_context.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/_registry.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/compact.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/matrix.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/sharing.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/stacked.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/type_curve.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/vector_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/template.docx +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/driver.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/cli.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/scaffold.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/sections/01-introduccion.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/sections/02-analisis.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/sections/03-conclusiones.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/slides/01-contexto.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/slides/02-analisis.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/slides/03-conclusiones.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/style/beamerthemeTesoro.sty +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/appendices/a-fuentes.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/chapters/01-introduccion.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/chapters/02-analisis.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/frontmatter/resumen.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/README.md.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/dot-gitignore +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/dot-vscode/settings.json +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/figures/dot-gitkeep +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/fonts/README.md +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/latexmkrc +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/refs.bib +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/style/tesorobrand.sty.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/style/tesorolocal.sty.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/orchestration.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/palette.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/providers/fred.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/providers/imf_irfcl.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/providers/lseg.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/table.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/document.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/scaffold.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/utils/template.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/cli.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/formatting.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/launcher.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/palette.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/plotting.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/scaffold.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/Dockerfile.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/README.md.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/dot-dockerignore.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/dot-gitignore.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/dot-streamlit/config.toml.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/pyproject.toml.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/__init__.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/cli.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/main.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/sections.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/tests/__init__.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/tests/test_app.py.tmpl +0 -0
|
@@ -66,6 +66,8 @@ from tesorotools.artists import (
|
|
|
66
66
|
Legend,
|
|
67
67
|
LinePlot,
|
|
68
68
|
MatrixChart,
|
|
69
|
+
SankeyPlot,
|
|
70
|
+
ScatterPlot,
|
|
69
71
|
ShockChart,
|
|
70
72
|
StackedAreaPlot,
|
|
71
73
|
StackedBarPlot,
|
|
@@ -108,6 +110,8 @@ def _register_builtins() -> None:
|
|
|
108
110
|
register_artist("shock_plot", ShockChart)
|
|
109
111
|
register_artist("intraday", IntradayPlot)
|
|
110
112
|
register_artist("waterfall", Waterfall)
|
|
113
|
+
register_artist("sankey", SankeyPlot)
|
|
114
|
+
register_artist("scatter", ScatterPlot)
|
|
111
115
|
|
|
112
116
|
register_tag("format", Format)
|
|
113
117
|
register_tag("legend", Legend)
|
|
@@ -149,6 +153,8 @@ __all__ = [
|
|
|
149
153
|
"MatrixChart",
|
|
150
154
|
"RegistryProtocol",
|
|
151
155
|
"Report",
|
|
156
|
+
"SankeyPlot",
|
|
157
|
+
"ScatterPlot",
|
|
152
158
|
"Section",
|
|
153
159
|
"ShockChart",
|
|
154
160
|
"StackedAreaPlot",
|
|
@@ -45,6 +45,8 @@ from tesorotools.artists.box_plot import BoxPlot
|
|
|
45
45
|
from tesorotools.artists.intraday_plot import IntradayPlot
|
|
46
46
|
from tesorotools.artists.line_plot import LinePlot
|
|
47
47
|
from tesorotools.artists.matrix import MatrixChart
|
|
48
|
+
from tesorotools.artists.sankey import SankeyPlot
|
|
49
|
+
from tesorotools.artists.scatter import ScatterPlot
|
|
48
50
|
from tesorotools.artists.sharing import share_y_axes
|
|
49
51
|
from tesorotools.artists.shock_plot import ShockChart
|
|
50
52
|
from tesorotools.artists.stacked import StackedAreaPlot, StackedBarPlot
|
|
@@ -64,6 +66,8 @@ __all__ = [
|
|
|
64
66
|
"Legend",
|
|
65
67
|
"LinePlot",
|
|
66
68
|
"MatrixChart",
|
|
69
|
+
"SankeyPlot",
|
|
70
|
+
"ScatterPlot",
|
|
67
71
|
"ShockChart",
|
|
68
72
|
"StackedAreaPlot",
|
|
69
73
|
"StackedBarPlot",
|
|
@@ -23,11 +23,13 @@ overrides only need to update ``plots.yaml``.
|
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
+
import math
|
|
26
27
|
from collections.abc import Sequence
|
|
27
28
|
from pathlib import Path
|
|
28
29
|
from typing import Any, Literal, Protocol, Self, cast, runtime_checkable
|
|
29
30
|
|
|
30
31
|
import matplotlib.pyplot as plt
|
|
32
|
+
import numpy as np
|
|
31
33
|
import pandas as pd
|
|
32
34
|
from matplotlib.axes import Axes
|
|
33
35
|
from matplotlib.dates import (
|
|
@@ -152,7 +154,7 @@ def plotly_unsupported(artist: object) -> frozenset[str]:
|
|
|
152
154
|
|
|
153
155
|
|
|
154
156
|
class FigureLifecycle:
|
|
155
|
-
"""``save()`` and ``
|
|
157
|
+
"""``save()``, ``plot()`` and ``to_tikz()`` on top of ``build()``.
|
|
156
158
|
|
|
157
159
|
Gives an artist the same three-step life cycle as the rest:
|
|
158
160
|
``build()`` returns the figure in memory, ``save()`` writes it
|
|
@@ -194,6 +196,17 @@ class FigureLifecycle:
|
|
|
194
196
|
plt.close(fig)
|
|
195
197
|
return ax
|
|
196
198
|
|
|
199
|
+
def to_tikz(self, path: Path | None = None) -> Path:
|
|
200
|
+
"""Write the chart as PGF code for LaTeX and return the path.
|
|
201
|
+
|
|
202
|
+
By default next to the PNG, with a ``.pgf`` suffix; the document
|
|
203
|
+
includes it with ``\\input``. Needs a TeX installation (see
|
|
204
|
+
:mod:`tesorotools.artists.tikz_backend`).
|
|
205
|
+
"""
|
|
206
|
+
from tesorotools.artists.tikz_backend import render_tikz
|
|
207
|
+
|
|
208
|
+
return render_tikz(self, path)
|
|
209
|
+
|
|
197
210
|
|
|
198
211
|
# ----------------------------------------------------------------------
|
|
199
212
|
# Structural contract for image artists
|
|
@@ -980,6 +993,40 @@ def apply_date_axis(
|
|
|
980
993
|
label.set_rotation_mode("anchor")
|
|
981
994
|
|
|
982
995
|
|
|
996
|
+
class DecimalsLocator(MaxNLocator):
|
|
997
|
+
"""Matplotlib's default y ticks, kept printable at *decimals*.
|
|
998
|
+
|
|
999
|
+
The default locator steps by 1, 2, 2.5 or 5 of a decade, so with a
|
|
1000
|
+
tick format of two decimals it can put ticks every 0.005, and two
|
|
1001
|
+
neighbours print alike (``1,17`` twice). When the step is not a
|
|
1002
|
+
whole multiple of ``10 ** -decimals`` (the unit) the next of 1, 2 or
|
|
1003
|
+
5 units takes its place: fewer ticks, all different. Otherwise the
|
|
1004
|
+
ticks are exactly the default ones.
|
|
1005
|
+
"""
|
|
1006
|
+
|
|
1007
|
+
def __init__(self, decimals: int, nbins: int | Literal["auto"] = "auto"):
|
|
1008
|
+
super().__init__( # type: ignore[reportUnknownMemberType]
|
|
1009
|
+
nbins=nbins, steps=[1, 2, 2.5, 5, 10]
|
|
1010
|
+
)
|
|
1011
|
+
self.decimals = decimals
|
|
1012
|
+
|
|
1013
|
+
def tick_values(self, vmin: float, vmax: float) -> Any:
|
|
1014
|
+
ticks = super().tick_values(vmin, vmax)
|
|
1015
|
+
if len(ticks) < 2:
|
|
1016
|
+
return ticks
|
|
1017
|
+
unit = 10.0**-self.decimals
|
|
1018
|
+
step = float(ticks[1] - ticks[0])
|
|
1019
|
+
ratio = step / unit
|
|
1020
|
+
if round(ratio) >= 1 and math.isclose(ratio, round(ratio)):
|
|
1021
|
+
return ticks
|
|
1022
|
+
# The default steps are 1, 2, 2.5 or 5 times a power of ten, so a
|
|
1023
|
+
# step that is no whole multiple of the unit is below 5 units.
|
|
1024
|
+
new = next(f * unit for f in (1, 2, 5) if f * unit >= step)
|
|
1025
|
+
low = math.floor(min(vmin, vmax) / new) * new
|
|
1026
|
+
high = math.ceil(max(vmin, vmax) / new) * new
|
|
1027
|
+
return np.arange(low, high + new / 2, new)
|
|
1028
|
+
|
|
1029
|
+
|
|
983
1030
|
def compact_y_axis(ax: Axes) -> None:
|
|
984
1031
|
"""Denser y-ticks for compact charts (more reference numbers).
|
|
985
1032
|
|
|
@@ -994,6 +1041,12 @@ def compact_y_axis(ax: Axes) -> None:
|
|
|
994
1041
|
raw: Any = COMPACT_CONFIG.get("y_max_ticks")
|
|
995
1042
|
if raw is None:
|
|
996
1043
|
return
|
|
1044
|
+
current = ax.yaxis.get_major_locator()
|
|
1045
|
+
if isinstance(current, DecimalsLocator):
|
|
1046
|
+
ax.yaxis.set_major_locator(
|
|
1047
|
+
DecimalsLocator(current.decimals, nbins=int(raw))
|
|
1048
|
+
)
|
|
1049
|
+
return
|
|
997
1050
|
ax.yaxis.set_major_locator(
|
|
998
1051
|
MaxNLocator(nbins=int(raw), steps=[1, 2, 2.5, 5, 10])
|
|
999
1052
|
)
|
|
@@ -1174,6 +1227,7 @@ def place_legend(
|
|
|
1174
1227
|
compact: bool,
|
|
1175
1228
|
ncol: int | None = None,
|
|
1176
1229
|
position: str = "auto",
|
|
1230
|
+
entries: tuple[list[Any], list[str]] | None = None,
|
|
1177
1231
|
) -> MplLegend | None:
|
|
1178
1232
|
"""Place the figure legend, compact-aware, from the labelled artists.
|
|
1179
1233
|
|
|
@@ -1184,9 +1238,14 @@ def place_legend(
|
|
|
1184
1238
|
``rcParams`` metrics. ``position`` (``"above"`` / ``"below"`` /
|
|
1185
1239
|
``"auto"``) chooses the side through :func:`resolve_legend_position`, so
|
|
1186
1240
|
a compact chart can carry its legend under the plot instead of over it.
|
|
1241
|
+
``entries`` passes precomputed ``(handles, labels)`` instead of reading
|
|
1242
|
+
them off *ax* -- for charts whose series span several axes (a
|
|
1243
|
+
``twinx`` secondary y-axis), in the order the legend should list them.
|
|
1187
1244
|
Returns ``None`` (drawing nothing) when there are no labels.
|
|
1188
1245
|
"""
|
|
1189
|
-
handles, labels =
|
|
1246
|
+
handles, labels = (
|
|
1247
|
+
entries if entries is not None else ax.get_legend_handles_labels()
|
|
1248
|
+
)
|
|
1190
1249
|
if not labels:
|
|
1191
1250
|
return None
|
|
1192
1251
|
side = resolve_legend_position(position, compact=compact)
|
|
@@ -1282,6 +1341,7 @@ def style_spines(
|
|
|
1282
1341
|
return format_annotation(y, decimals, units)
|
|
1283
1342
|
|
|
1284
1343
|
ax.yaxis.set_major_formatter(FuncFormatter(_fmt_tick))
|
|
1344
|
+
ax.yaxis.set_major_locator(DecimalsLocator(decimals))
|
|
1285
1345
|
ax.set_xlabel( # type: ignore[reportUnknownMemberType]
|
|
1286
1346
|
""
|
|
1287
1347
|
)
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""Bar + line combo chart artist, registered as ``!bar_line``.
|
|
2
2
|
|
|
3
3
|
Defines :class:`BarLinePlot`: one time-series chart that draws some
|
|
4
|
-
series as **vertical bars** and others as **marked lines
|
|
5
|
-
shared y-axis. The canonical use is a secondary magnitude in
|
|
6
|
-
earnings *surprise* in p.p.) under a primary series as the
|
|
7
|
-
line (earnings *growth* in %), both read against the same 0
|
|
4
|
+
series as **vertical bars** and others as **marked lines**, by default
|
|
5
|
+
on a single shared y-axis. The canonical use is a secondary magnitude in
|
|
6
|
+
bars (an earnings *surprise* in p.p.) under a primary series as the
|
|
7
|
+
highlighted line (earnings *growth* in %), both read against the same 0
|
|
8
|
+
baseline. When the scales are too far apart to share an axis (an
|
|
9
|
+
exchange rate around 1.2 against an R^2 between 0 and 1), ``secondary``
|
|
10
|
+
moves any subset of the series, bars or lines, to a right-hand y-axis
|
|
11
|
+
with its own scale, format and title.
|
|
8
12
|
|
|
9
13
|
Unlike :class:`LinePlot`, the x-axis is **categorical**: one slot per
|
|
10
14
|
observation (``np.arange``) with date-formatted tick labels, so the bars
|
|
@@ -38,6 +42,7 @@ from matplotlib.figure import Figure
|
|
|
38
42
|
from yaml.nodes import MappingNode
|
|
39
43
|
|
|
40
44
|
from tesorotools.artists._common import (
|
|
45
|
+
FigureLifecycle,
|
|
41
46
|
AX_CONFIG,
|
|
42
47
|
Format,
|
|
43
48
|
Legend,
|
|
@@ -62,8 +67,8 @@ __all__ = ["BarLinePlot"]
|
|
|
62
67
|
BAR_LINE_CONFIG: dict[str, Any] = PLOT_CONFIG["bar_line"]
|
|
63
68
|
|
|
64
69
|
|
|
65
|
-
class BarLinePlot:
|
|
66
|
-
"""Combo chart: vertical bars and marked lines on
|
|
70
|
+
class BarLinePlot(FigureLifecycle):
|
|
71
|
+
"""Combo chart: vertical bars and marked lines, optionally on two axes.
|
|
67
72
|
|
|
68
73
|
Parameters mirror the other time-series artists where they overlap
|
|
69
74
|
(``out_path``, ``data`` / ``data_path``, ``scale``, ``start_date`` /
|
|
@@ -87,6 +92,21 @@ class BarLinePlot:
|
|
|
87
92
|
``date_format`` is applied via ``strftime`` (lower-cased, e.g.
|
|
88
93
|
``"dic-21"``) to a DatetimeIndex; ``max_xticks`` thins the labels
|
|
89
94
|
(``None`` labels every slot).
|
|
95
|
+
* ``secondary`` -- ids (from ``bars`` and/or ``lines``) drawn against a
|
|
96
|
+
second y-axis on the right, with its own scale. Empty (the default)
|
|
97
|
+
keeps the single shared axis, ticked on the house side (right). With
|
|
98
|
+
a secondary axis the primary one moves to the left, its grid is the
|
|
99
|
+
only one drawn, and the legend still lists every series in declared
|
|
100
|
+
order (bars, then lines). It cannot hold every series: the primary
|
|
101
|
+
axis would be left empty.
|
|
102
|
+
* ``secondary_fmt`` -- tick format (``!format``) of the secondary axis;
|
|
103
|
+
defaults to ``fmt``.
|
|
104
|
+
* ``ylabel`` / ``secondary_ylabel`` -- y-axis titles (empty: none).
|
|
105
|
+
``ylabel`` sits on the primary axis' tick side.
|
|
106
|
+
|
|
107
|
+
``baseline`` draws the 0 line on every axis that holds bars (they grow
|
|
108
|
+
from it) or, with no bars at all, on the primary axis, so a lines-only
|
|
109
|
+
axis is never stretched down to 0.
|
|
90
110
|
"""
|
|
91
111
|
|
|
92
112
|
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
@@ -102,6 +122,7 @@ class BarLinePlot:
|
|
|
102
122
|
"legend.ncol",
|
|
103
123
|
"max_xticks",
|
|
104
124
|
"plot_size",
|
|
125
|
+
"secondary_fmt.decimals",
|
|
105
126
|
"x_rotation",
|
|
106
127
|
}
|
|
107
128
|
)
|
|
@@ -130,6 +151,10 @@ class BarLinePlot:
|
|
|
130
151
|
max_xticks: int | None = None,
|
|
131
152
|
compact: bool = False,
|
|
132
153
|
font_pt: float | None = None,
|
|
154
|
+
secondary: list[str] | None = None,
|
|
155
|
+
secondary_fmt: Format | None = None,
|
|
156
|
+
ylabel: str = "",
|
|
157
|
+
secondary_ylabel: str = "",
|
|
133
158
|
) -> None:
|
|
134
159
|
if out_path.suffix != ".png":
|
|
135
160
|
raise ValueError(f"out_path must be .png: {out_path}")
|
|
@@ -139,6 +164,17 @@ class BarLinePlot:
|
|
|
139
164
|
raise ValueError(
|
|
140
165
|
"BarLinePlot needs at least one series in `bars` or `lines`"
|
|
141
166
|
)
|
|
167
|
+
self.secondary = list(secondary or [])
|
|
168
|
+
unknown = [c for c in self.secondary if c not in self.bars | self.lines]
|
|
169
|
+
if unknown:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"`secondary` ids must be in `bars` or `lines`: {unknown}"
|
|
172
|
+
)
|
|
173
|
+
if set(self.secondary) >= {*self.bars, *self.lines}:
|
|
174
|
+
raise ValueError(
|
|
175
|
+
"`secondary` cannot hold every series: the primary axis "
|
|
176
|
+
"would be empty"
|
|
177
|
+
)
|
|
142
178
|
self.out_path = out_path
|
|
143
179
|
self.data = resolve_data(data, data_path)
|
|
144
180
|
self.scale = scale
|
|
@@ -157,6 +193,9 @@ class BarLinePlot:
|
|
|
157
193
|
self.max_xticks = max_xticks
|
|
158
194
|
self.compact = compact
|
|
159
195
|
self.font_pt = font_pt
|
|
196
|
+
self.secondary_fmt = secondary_fmt or self.fmt
|
|
197
|
+
self.ylabel = ylabel
|
|
198
|
+
self.secondary_ylabel = secondary_ylabel
|
|
160
199
|
|
|
161
200
|
@classmethod
|
|
162
201
|
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
@@ -179,7 +218,8 @@ class BarLinePlot:
|
|
|
179
218
|
``fmt`` (``!format``), ``legend`` (``!legend``), ``figsize``,
|
|
180
219
|
``plot_size``, ``bar_styles`` / ``line_styles``, ``bar_width``,
|
|
181
220
|
``date_format``, ``x_rotation``, ``max_xticks``, ``compact``,
|
|
182
|
-
``font_pt
|
|
221
|
+
``font_pt``, ``secondary``, ``secondary_fmt`` (``!format``),
|
|
222
|
+
``ylabel`` / ``secondary_ylabel``.
|
|
183
223
|
|
|
184
224
|
Example
|
|
185
225
|
-------
|
|
@@ -194,6 +234,26 @@ class BarLinePlot:
|
|
|
194
234
|
surprise: "Sorpresa (pp)"
|
|
195
235
|
fmt: !format {decimals: 0}
|
|
196
236
|
legend: !legend {}
|
|
237
|
+
|
|
238
|
+
With scales too far apart for one axis, send some series to the
|
|
239
|
+
right-hand axis:
|
|
240
|
+
|
|
241
|
+
.. code-block:: yaml
|
|
242
|
+
|
|
243
|
+
eurusd_fair_value: !bar_line
|
|
244
|
+
out_path: out/eurusd_fair_value.png
|
|
245
|
+
data_path: data/eurusd.feather
|
|
246
|
+
bars:
|
|
247
|
+
r2: "R^2"
|
|
248
|
+
lines:
|
|
249
|
+
eurusd: "EURUSD"
|
|
250
|
+
fair_value: "Modelo valor justo (60 días)"
|
|
251
|
+
secondary: [r2]
|
|
252
|
+
fmt: !format {decimals: 2}
|
|
253
|
+
secondary_fmt: !format {decimals: 1}
|
|
254
|
+
ylabel: "EURUSD"
|
|
255
|
+
secondary_ylabel: "Coeficiente de determinación (R^2)"
|
|
256
|
+
legend: !legend {}
|
|
197
257
|
"""
|
|
198
258
|
cfg: dict[str, Any] = loader.construct_mapping( # type: ignore[assignment]
|
|
199
259
|
node, deep=True
|
|
@@ -287,9 +347,13 @@ class BarLinePlot:
|
|
|
287
347
|
**compact_figure_kwargs(self.compact, self.figsize)
|
|
288
348
|
)
|
|
289
349
|
ax: Axes = fig.add_subplot()
|
|
350
|
+
ax2: Axes | None = ax.twinx() if self.secondary else None
|
|
351
|
+
|
|
352
|
+
def target(col: str) -> Axes:
|
|
353
|
+
return ax2 if ax2 is not None and col in self.secondary else ax
|
|
290
354
|
|
|
291
355
|
# Bars first (behind the lines). Several bar series share each slot,
|
|
292
|
-
# dodged side by side; a single
|
|
356
|
+
# dodged side by side (across both axes); a single one is centred.
|
|
293
357
|
n_bars = len(self.bars)
|
|
294
358
|
each = self.bar_width / n_bars if n_bars else self.bar_width
|
|
295
359
|
default_alpha: float = float(BAR_LINE_CONFIG["bar"].get("alpha", 0.9))
|
|
@@ -299,7 +363,7 @@ class BarLinePlot:
|
|
|
299
363
|
style.setdefault("alpha", default_alpha)
|
|
300
364
|
offset = (j - (n_bars - 1) / 2) * each
|
|
301
365
|
values = plot_data[col].fillna(0).to_numpy(dtype=np.float64)
|
|
302
|
-
|
|
366
|
+
target(col).bar( # type: ignore[reportUnknownMemberType]
|
|
303
367
|
x + offset,
|
|
304
368
|
values,
|
|
305
369
|
width=each,
|
|
@@ -317,7 +381,7 @@ class BarLinePlot:
|
|
|
317
381
|
style.setdefault("marker", line_cfg.get("marker", "o"))
|
|
318
382
|
style.setdefault("linewidth", line_cfg.get("linewidth", 2))
|
|
319
383
|
style.setdefault("markersize", line_cfg.get("markersize", 5))
|
|
320
|
-
|
|
384
|
+
target(col).plot( # type: ignore[reportUnknownMemberType]
|
|
321
385
|
x,
|
|
322
386
|
plot_data[col].to_numpy(dtype=np.float64),
|
|
323
387
|
label=label,
|
|
@@ -336,14 +400,31 @@ class BarLinePlot:
|
|
|
336
400
|
# style_spines enables the y grid only (axis="y"), which is what these
|
|
337
401
|
# charts read against; the categorical x-axis stays gridless.
|
|
338
402
|
if self.baseline:
|
|
339
|
-
|
|
340
|
-
|
|
403
|
+
on_primary, on_secondary = self._baseline_on()
|
|
404
|
+
for axis, on in ((ax, on_primary), (ax2, on_secondary)):
|
|
405
|
+
if on and axis is not None:
|
|
406
|
+
style_baseline(axis, 0, **AX_CONFIG["baseline"])
|
|
407
|
+
|
|
408
|
+
side = "right"
|
|
409
|
+
if ax2 is not None:
|
|
410
|
+
side = "left"
|
|
411
|
+
ax.yaxis.tick_left()
|
|
412
|
+
self._style_secondary(ax, ax2, font_pt)
|
|
341
413
|
if self.compact:
|
|
342
|
-
style_compact_axes(ax, font_pt, side=
|
|
414
|
+
style_compact_axes(ax, font_pt, side=side)
|
|
415
|
+
if self.ylabel:
|
|
416
|
+
ax.yaxis.set_label_position(side) # type: ignore[reportArgumentType]
|
|
417
|
+
ax.set_ylabel( # type: ignore[reportUnknownMemberType]
|
|
418
|
+
self.ylabel, fontsize=font_pt
|
|
419
|
+
)
|
|
343
420
|
|
|
344
421
|
if self.legend is not None:
|
|
345
422
|
place_legend(
|
|
346
|
-
fig,
|
|
423
|
+
fig,
|
|
424
|
+
ax,
|
|
425
|
+
compact=self.compact,
|
|
426
|
+
entries=self._legend_entries(ax, ax2),
|
|
427
|
+
**legend_layout(self.legend),
|
|
347
428
|
)
|
|
348
429
|
|
|
349
430
|
if self.plot_size is not None and not self.compact:
|
|
@@ -351,6 +432,58 @@ class BarLinePlot:
|
|
|
351
432
|
|
|
352
433
|
return fig, ax
|
|
353
434
|
|
|
435
|
+
def _baseline_on(self) -> tuple[bool, bool]:
|
|
436
|
+
"""``(primary, secondary)``: the axes that get the 0 line.
|
|
437
|
+
|
|
438
|
+
Those holding bars (they grow from 0) or, with no bars at all, the
|
|
439
|
+
primary one. Shared by :meth:`build` and :meth:`to_plotly`.
|
|
440
|
+
"""
|
|
441
|
+
on_secondary = [c in self.secondary for c in self.bars]
|
|
442
|
+
secondary = any(on_secondary)
|
|
443
|
+
primary = not all(on_secondary) or not secondary
|
|
444
|
+
return primary, secondary
|
|
445
|
+
|
|
446
|
+
def _style_secondary(
|
|
447
|
+
self, ax: Axes, ax2: Axes, font_pt: float | None
|
|
448
|
+
) -> None:
|
|
449
|
+
"""Style the right-hand axis: own format, no grid, title, z-order.
|
|
450
|
+
|
|
451
|
+
The twin is drawn above the primary by default, which would hide
|
|
452
|
+
the primary series behind secondary bars; when the secondary holds
|
|
453
|
+
bars the primary is brought forward with a transparent background.
|
|
454
|
+
"""
|
|
455
|
+
style_spines(
|
|
456
|
+
ax2,
|
|
457
|
+
decimals=self.secondary_fmt.decimals,
|
|
458
|
+
units=self.secondary_fmt.units,
|
|
459
|
+
**AX_CONFIG["spines"],
|
|
460
|
+
)
|
|
461
|
+
ax2.grid(False) # type: ignore[reportUnknownMemberType]
|
|
462
|
+
if self.compact:
|
|
463
|
+
style_compact_axes(ax2, font_pt, side="right")
|
|
464
|
+
if self.secondary_ylabel:
|
|
465
|
+
ax2.set_ylabel( # type: ignore[reportUnknownMemberType]
|
|
466
|
+
self.secondary_ylabel, fontsize=font_pt
|
|
467
|
+
)
|
|
468
|
+
if any(c in self.secondary for c in self.bars):
|
|
469
|
+
ax.set_zorder(ax2.get_zorder() + 1)
|
|
470
|
+
ax.patch.set_visible(False)
|
|
471
|
+
|
|
472
|
+
def _legend_entries(
|
|
473
|
+
self, ax: Axes, ax2: Axes | None
|
|
474
|
+
) -> tuple[list[Any], list[str]]:
|
|
475
|
+
"""Handles and labels of both axes, in declared order."""
|
|
476
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
477
|
+
if ax2 is not None:
|
|
478
|
+
more_handles, more_labels = ax2.get_legend_handles_labels()
|
|
479
|
+
handles, labels = handles + more_handles, labels + more_labels
|
|
480
|
+
order = [*self.bars.values(), *self.lines.values()]
|
|
481
|
+
pairs = sorted(
|
|
482
|
+
zip(handles, labels, strict=True),
|
|
483
|
+
key=lambda pair: order.index(pair[1]),
|
|
484
|
+
)
|
|
485
|
+
return [h for h, _ in pairs], [label for _, label in pairs]
|
|
486
|
+
|
|
354
487
|
def save(
|
|
355
488
|
self,
|
|
356
489
|
fig: Figure,
|
|
@@ -44,6 +44,7 @@ from matplotlib.patches import Patch
|
|
|
44
44
|
from yaml.nodes import MappingNode
|
|
45
45
|
|
|
46
46
|
from tesorotools.artists._common import (
|
|
47
|
+
FigureLifecycle,
|
|
47
48
|
AX_CONFIG,
|
|
48
49
|
Format,
|
|
49
50
|
Legend,
|
|
@@ -105,7 +106,7 @@ def _weighted_quantile(
|
|
|
105
106
|
return float(np.interp(q, cum, v))
|
|
106
107
|
|
|
107
108
|
|
|
108
|
-
class BoxPlot:
|
|
109
|
+
class BoxPlot(FigureLifecycle):
|
|
109
110
|
"""Box-and-whisker chart, one box per category.
|
|
110
111
|
|
|
111
112
|
Parameters mirror the other artists where they overlap (``out_path``,
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.4}/src/tesorotools/artists/intraday_plot.py
RENAMED
|
@@ -42,6 +42,7 @@ from matplotlib.figure import Figure
|
|
|
42
42
|
from yaml.nodes import MappingNode
|
|
43
43
|
|
|
44
44
|
from tesorotools.artists._common import (
|
|
45
|
+
FigureLifecycle,
|
|
45
46
|
AX_CONFIG,
|
|
46
47
|
Format,
|
|
47
48
|
Legend,
|
|
@@ -66,7 +67,7 @@ __all__ = ["IntradayPlot"]
|
|
|
66
67
|
_MAX_XTICKS = 10
|
|
67
68
|
|
|
68
69
|
|
|
69
|
-
class IntradayPlot:
|
|
70
|
+
class IntradayPlot(FigureLifecycle):
|
|
70
71
|
"""Single-instrument intraday line chart on a gap-free ordinal axis.
|
|
71
72
|
|
|
72
73
|
Parameters mirror the other image artists where they overlap
|
|
@@ -23,6 +23,8 @@ from matplotlib.ticker import FuncFormatter
|
|
|
23
23
|
from yaml.nodes import MappingNode
|
|
24
24
|
|
|
25
25
|
from tesorotools.artists._common import (
|
|
26
|
+
DecimalsLocator,
|
|
27
|
+
FigureLifecycle,
|
|
26
28
|
AX_CONFIG,
|
|
27
29
|
DATE_DENSITY_LEVELS,
|
|
28
30
|
Format,
|
|
@@ -56,7 +58,7 @@ locale.setlocale(locale.LC_ALL, "")
|
|
|
56
58
|
__all__ = ["LinePlot"]
|
|
57
59
|
|
|
58
60
|
|
|
59
|
-
class LinePlot:
|
|
61
|
+
class LinePlot(FigureLifecycle):
|
|
60
62
|
"""Time-series line chart with the tesorotools visual style.
|
|
61
63
|
|
|
62
64
|
Parameters mirror the other image artists where they
|
|
@@ -542,6 +544,7 @@ class LinePlot:
|
|
|
542
544
|
return format_annotation(y, axis_decimals, units)
|
|
543
545
|
|
|
544
546
|
ax.yaxis.set_major_formatter(FuncFormatter(_fmt_axis_tick))
|
|
547
|
+
ax.yaxis.set_major_locator(DecimalsLocator(axis_decimals))
|
|
545
548
|
if self.baseline:
|
|
546
549
|
reference = 100 if self.base_100 else 0
|
|
547
550
|
style_baseline(ax, reference, **AX_CONFIG["baseline"])
|