tesorotools-python 0.1.2__tar.gz → 0.1.3__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.3}/PKG-INFO +1 -1
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/_common.py +7 -1
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/bar_line.py +145 -13
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/plotly_backend.py +29 -5
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/.gitignore +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/pyproject.toml +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/_build_context.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/_registry.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/box_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/compact.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/intraday_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/line_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/matrix.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/sharing.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/shock_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/stacked.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/type_curve.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/vector_plot.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/waterfall.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/plots.yaml +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/template.docx +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/driver.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/cli.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/scaffold.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/article/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/article/sections/01-introduccion.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/article/sections/02-analisis.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/article/sections/03-conclusiones.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/beamer/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/beamer/slides/01-contexto.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/beamer/slides/02-analisis.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/beamer/slides/03-conclusiones.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/beamer/style/beamerthemeTesoro.sty +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/book/appendices/a-fuentes.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/book/chapters/01-introduccion.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/book/chapters/02-analisis.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/book/frontmatter/resumen.tex +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/book/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/README.md.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/dot-gitignore +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/dot-vscode/settings.json +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/figures/dot-gitkeep +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/fonts/README.md +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/latexmkrc +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/refs.bib +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/style/tesoro.sty +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/style/tesorobrand.sty.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/latex/templates/common/style/tesorolocal.sty.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/orchestration.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/palette.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/fred.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/imf_irfcl.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/lseg.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/table.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/document.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/scaffold.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/utils/template.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/__init__.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/app.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/brand.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/cli.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/components.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/formatting.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/launcher.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/palette.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/plotting.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/scaffold.py +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/Dockerfile.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/README.md.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/dot-dockerignore.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/dot-gitignore.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/dot-streamlit/config.toml.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/pyproject.toml.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/src/$package/__init__.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/src/$package/cli.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/src/$package/main.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/src/$package/sections.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/tests/__init__.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/tests/test_app.py.tmpl +0 -0
- {tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/theme.py +0 -0
|
@@ -1174,6 +1174,7 @@ def place_legend(
|
|
|
1174
1174
|
compact: bool,
|
|
1175
1175
|
ncol: int | None = None,
|
|
1176
1176
|
position: str = "auto",
|
|
1177
|
+
entries: tuple[list[Any], list[str]] | None = None,
|
|
1177
1178
|
) -> MplLegend | None:
|
|
1178
1179
|
"""Place the figure legend, compact-aware, from the labelled artists.
|
|
1179
1180
|
|
|
@@ -1184,9 +1185,14 @@ def place_legend(
|
|
|
1184
1185
|
``rcParams`` metrics. ``position`` (``"above"`` / ``"below"`` /
|
|
1185
1186
|
``"auto"``) chooses the side through :func:`resolve_legend_position`, so
|
|
1186
1187
|
a compact chart can carry its legend under the plot instead of over it.
|
|
1188
|
+
``entries`` passes precomputed ``(handles, labels)`` instead of reading
|
|
1189
|
+
them off *ax* -- for charts whose series span several axes (a
|
|
1190
|
+
``twinx`` secondary y-axis), in the order the legend should list them.
|
|
1187
1191
|
Returns ``None`` (drawing nothing) when there are no labels.
|
|
1188
1192
|
"""
|
|
1189
|
-
handles, labels =
|
|
1193
|
+
handles, labels = (
|
|
1194
|
+
entries if entries is not None else ax.get_legend_handles_labels()
|
|
1195
|
+
)
|
|
1190
1196
|
if not labels:
|
|
1191
1197
|
return None
|
|
1192
1198
|
side = resolve_legend_position(position, compact=compact)
|
|
@@ -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
|
|
@@ -63,7 +67,7 @@ BAR_LINE_CONFIG: dict[str, Any] = PLOT_CONFIG["bar_line"]
|
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
class BarLinePlot:
|
|
66
|
-
"""Combo chart: vertical bars and marked lines on
|
|
70
|
+
"""Combo chart: vertical bars and marked lines, optionally on two axes.
|
|
67
71
|
|
|
68
72
|
Parameters mirror the other time-series artists where they overlap
|
|
69
73
|
(``out_path``, ``data`` / ``data_path``, ``scale``, ``start_date`` /
|
|
@@ -87,6 +91,21 @@ class BarLinePlot:
|
|
|
87
91
|
``date_format`` is applied via ``strftime`` (lower-cased, e.g.
|
|
88
92
|
``"dic-21"``) to a DatetimeIndex; ``max_xticks`` thins the labels
|
|
89
93
|
(``None`` labels every slot).
|
|
94
|
+
* ``secondary`` -- ids (from ``bars`` and/or ``lines``) drawn against a
|
|
95
|
+
second y-axis on the right, with its own scale. Empty (the default)
|
|
96
|
+
keeps the single shared axis, ticked on the house side (right). With
|
|
97
|
+
a secondary axis the primary one moves to the left, its grid is the
|
|
98
|
+
only one drawn, and the legend still lists every series in declared
|
|
99
|
+
order (bars, then lines). It cannot hold every series: the primary
|
|
100
|
+
axis would be left empty.
|
|
101
|
+
* ``secondary_fmt`` -- tick format (``!format``) of the secondary axis;
|
|
102
|
+
defaults to ``fmt``.
|
|
103
|
+
* ``ylabel`` / ``secondary_ylabel`` -- y-axis titles (empty: none).
|
|
104
|
+
``ylabel`` sits on the primary axis' tick side.
|
|
105
|
+
|
|
106
|
+
``baseline`` draws the 0 line on every axis that holds bars (they grow
|
|
107
|
+
from it) or, with no bars at all, on the primary axis, so a lines-only
|
|
108
|
+
axis is never stretched down to 0.
|
|
90
109
|
"""
|
|
91
110
|
|
|
92
111
|
#: Keys that change the PNG but never the ``to_plotly()`` figure
|
|
@@ -102,6 +121,7 @@ class BarLinePlot:
|
|
|
102
121
|
"legend.ncol",
|
|
103
122
|
"max_xticks",
|
|
104
123
|
"plot_size",
|
|
124
|
+
"secondary_fmt.decimals",
|
|
105
125
|
"x_rotation",
|
|
106
126
|
}
|
|
107
127
|
)
|
|
@@ -130,6 +150,10 @@ class BarLinePlot:
|
|
|
130
150
|
max_xticks: int | None = None,
|
|
131
151
|
compact: bool = False,
|
|
132
152
|
font_pt: float | None = None,
|
|
153
|
+
secondary: list[str] | None = None,
|
|
154
|
+
secondary_fmt: Format | None = None,
|
|
155
|
+
ylabel: str = "",
|
|
156
|
+
secondary_ylabel: str = "",
|
|
133
157
|
) -> None:
|
|
134
158
|
if out_path.suffix != ".png":
|
|
135
159
|
raise ValueError(f"out_path must be .png: {out_path}")
|
|
@@ -139,6 +163,17 @@ class BarLinePlot:
|
|
|
139
163
|
raise ValueError(
|
|
140
164
|
"BarLinePlot needs at least one series in `bars` or `lines`"
|
|
141
165
|
)
|
|
166
|
+
self.secondary = list(secondary or [])
|
|
167
|
+
unknown = [c for c in self.secondary if c not in self.bars | self.lines]
|
|
168
|
+
if unknown:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"`secondary` ids must be in `bars` or `lines`: {unknown}"
|
|
171
|
+
)
|
|
172
|
+
if set(self.secondary) >= {*self.bars, *self.lines}:
|
|
173
|
+
raise ValueError(
|
|
174
|
+
"`secondary` cannot hold every series: the primary axis "
|
|
175
|
+
"would be empty"
|
|
176
|
+
)
|
|
142
177
|
self.out_path = out_path
|
|
143
178
|
self.data = resolve_data(data, data_path)
|
|
144
179
|
self.scale = scale
|
|
@@ -157,6 +192,9 @@ class BarLinePlot:
|
|
|
157
192
|
self.max_xticks = max_xticks
|
|
158
193
|
self.compact = compact
|
|
159
194
|
self.font_pt = font_pt
|
|
195
|
+
self.secondary_fmt = secondary_fmt or self.fmt
|
|
196
|
+
self.ylabel = ylabel
|
|
197
|
+
self.secondary_ylabel = secondary_ylabel
|
|
160
198
|
|
|
161
199
|
@classmethod
|
|
162
200
|
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
@@ -179,7 +217,8 @@ class BarLinePlot:
|
|
|
179
217
|
``fmt`` (``!format``), ``legend`` (``!legend``), ``figsize``,
|
|
180
218
|
``plot_size``, ``bar_styles`` / ``line_styles``, ``bar_width``,
|
|
181
219
|
``date_format``, ``x_rotation``, ``max_xticks``, ``compact``,
|
|
182
|
-
``font_pt
|
|
220
|
+
``font_pt``, ``secondary``, ``secondary_fmt`` (``!format``),
|
|
221
|
+
``ylabel`` / ``secondary_ylabel``.
|
|
183
222
|
|
|
184
223
|
Example
|
|
185
224
|
-------
|
|
@@ -194,6 +233,26 @@ class BarLinePlot:
|
|
|
194
233
|
surprise: "Sorpresa (pp)"
|
|
195
234
|
fmt: !format {decimals: 0}
|
|
196
235
|
legend: !legend {}
|
|
236
|
+
|
|
237
|
+
With scales too far apart for one axis, send some series to the
|
|
238
|
+
right-hand axis:
|
|
239
|
+
|
|
240
|
+
.. code-block:: yaml
|
|
241
|
+
|
|
242
|
+
eurusd_fair_value: !bar_line
|
|
243
|
+
out_path: out/eurusd_fair_value.png
|
|
244
|
+
data_path: data/eurusd.feather
|
|
245
|
+
bars:
|
|
246
|
+
r2: "R^2"
|
|
247
|
+
lines:
|
|
248
|
+
eurusd: "EURUSD"
|
|
249
|
+
fair_value: "Modelo valor justo (60 días)"
|
|
250
|
+
secondary: [r2]
|
|
251
|
+
fmt: !format {decimals: 2}
|
|
252
|
+
secondary_fmt: !format {decimals: 1}
|
|
253
|
+
ylabel: "EURUSD"
|
|
254
|
+
secondary_ylabel: "Coeficiente de determinación (R^2)"
|
|
255
|
+
legend: !legend {}
|
|
197
256
|
"""
|
|
198
257
|
cfg: dict[str, Any] = loader.construct_mapping( # type: ignore[assignment]
|
|
199
258
|
node, deep=True
|
|
@@ -287,9 +346,13 @@ class BarLinePlot:
|
|
|
287
346
|
**compact_figure_kwargs(self.compact, self.figsize)
|
|
288
347
|
)
|
|
289
348
|
ax: Axes = fig.add_subplot()
|
|
349
|
+
ax2: Axes | None = ax.twinx() if self.secondary else None
|
|
350
|
+
|
|
351
|
+
def target(col: str) -> Axes:
|
|
352
|
+
return ax2 if ax2 is not None and col in self.secondary else ax
|
|
290
353
|
|
|
291
354
|
# Bars first (behind the lines). Several bar series share each slot,
|
|
292
|
-
# dodged side by side; a single
|
|
355
|
+
# dodged side by side (across both axes); a single one is centred.
|
|
293
356
|
n_bars = len(self.bars)
|
|
294
357
|
each = self.bar_width / n_bars if n_bars else self.bar_width
|
|
295
358
|
default_alpha: float = float(BAR_LINE_CONFIG["bar"].get("alpha", 0.9))
|
|
@@ -299,7 +362,7 @@ class BarLinePlot:
|
|
|
299
362
|
style.setdefault("alpha", default_alpha)
|
|
300
363
|
offset = (j - (n_bars - 1) / 2) * each
|
|
301
364
|
values = plot_data[col].fillna(0).to_numpy(dtype=np.float64)
|
|
302
|
-
|
|
365
|
+
target(col).bar( # type: ignore[reportUnknownMemberType]
|
|
303
366
|
x + offset,
|
|
304
367
|
values,
|
|
305
368
|
width=each,
|
|
@@ -317,7 +380,7 @@ class BarLinePlot:
|
|
|
317
380
|
style.setdefault("marker", line_cfg.get("marker", "o"))
|
|
318
381
|
style.setdefault("linewidth", line_cfg.get("linewidth", 2))
|
|
319
382
|
style.setdefault("markersize", line_cfg.get("markersize", 5))
|
|
320
|
-
|
|
383
|
+
target(col).plot( # type: ignore[reportUnknownMemberType]
|
|
321
384
|
x,
|
|
322
385
|
plot_data[col].to_numpy(dtype=np.float64),
|
|
323
386
|
label=label,
|
|
@@ -336,14 +399,31 @@ class BarLinePlot:
|
|
|
336
399
|
# style_spines enables the y grid only (axis="y"), which is what these
|
|
337
400
|
# charts read against; the categorical x-axis stays gridless.
|
|
338
401
|
if self.baseline:
|
|
339
|
-
|
|
340
|
-
|
|
402
|
+
on_primary, on_secondary = self._baseline_on()
|
|
403
|
+
for axis, on in ((ax, on_primary), (ax2, on_secondary)):
|
|
404
|
+
if on and axis is not None:
|
|
405
|
+
style_baseline(axis, 0, **AX_CONFIG["baseline"])
|
|
406
|
+
|
|
407
|
+
side = "right"
|
|
408
|
+
if ax2 is not None:
|
|
409
|
+
side = "left"
|
|
410
|
+
ax.yaxis.tick_left()
|
|
411
|
+
self._style_secondary(ax, ax2, font_pt)
|
|
341
412
|
if self.compact:
|
|
342
|
-
style_compact_axes(ax, font_pt, side=
|
|
413
|
+
style_compact_axes(ax, font_pt, side=side)
|
|
414
|
+
if self.ylabel:
|
|
415
|
+
ax.yaxis.set_label_position(side) # type: ignore[reportArgumentType]
|
|
416
|
+
ax.set_ylabel( # type: ignore[reportUnknownMemberType]
|
|
417
|
+
self.ylabel, fontsize=font_pt
|
|
418
|
+
)
|
|
343
419
|
|
|
344
420
|
if self.legend is not None:
|
|
345
421
|
place_legend(
|
|
346
|
-
fig,
|
|
422
|
+
fig,
|
|
423
|
+
ax,
|
|
424
|
+
compact=self.compact,
|
|
425
|
+
entries=self._legend_entries(ax, ax2),
|
|
426
|
+
**legend_layout(self.legend),
|
|
347
427
|
)
|
|
348
428
|
|
|
349
429
|
if self.plot_size is not None and not self.compact:
|
|
@@ -351,6 +431,58 @@ class BarLinePlot:
|
|
|
351
431
|
|
|
352
432
|
return fig, ax
|
|
353
433
|
|
|
434
|
+
def _baseline_on(self) -> tuple[bool, bool]:
|
|
435
|
+
"""``(primary, secondary)``: the axes that get the 0 line.
|
|
436
|
+
|
|
437
|
+
Those holding bars (they grow from 0) or, with no bars at all, the
|
|
438
|
+
primary one. Shared by :meth:`build` and :meth:`to_plotly`.
|
|
439
|
+
"""
|
|
440
|
+
on_secondary = [c in self.secondary for c in self.bars]
|
|
441
|
+
secondary = any(on_secondary)
|
|
442
|
+
primary = not all(on_secondary) or not secondary
|
|
443
|
+
return primary, secondary
|
|
444
|
+
|
|
445
|
+
def _style_secondary(
|
|
446
|
+
self, ax: Axes, ax2: Axes, font_pt: float | None
|
|
447
|
+
) -> None:
|
|
448
|
+
"""Style the right-hand axis: own format, no grid, title, z-order.
|
|
449
|
+
|
|
450
|
+
The twin is drawn above the primary by default, which would hide
|
|
451
|
+
the primary series behind secondary bars; when the secondary holds
|
|
452
|
+
bars the primary is brought forward with a transparent background.
|
|
453
|
+
"""
|
|
454
|
+
style_spines(
|
|
455
|
+
ax2,
|
|
456
|
+
decimals=self.secondary_fmt.decimals,
|
|
457
|
+
units=self.secondary_fmt.units,
|
|
458
|
+
**AX_CONFIG["spines"],
|
|
459
|
+
)
|
|
460
|
+
ax2.grid(False) # type: ignore[reportUnknownMemberType]
|
|
461
|
+
if self.compact:
|
|
462
|
+
style_compact_axes(ax2, font_pt, side="right")
|
|
463
|
+
if self.secondary_ylabel:
|
|
464
|
+
ax2.set_ylabel( # type: ignore[reportUnknownMemberType]
|
|
465
|
+
self.secondary_ylabel, fontsize=font_pt
|
|
466
|
+
)
|
|
467
|
+
if any(c in self.secondary for c in self.bars):
|
|
468
|
+
ax.set_zorder(ax2.get_zorder() + 1)
|
|
469
|
+
ax.patch.set_visible(False)
|
|
470
|
+
|
|
471
|
+
def _legend_entries(
|
|
472
|
+
self, ax: Axes, ax2: Axes | None
|
|
473
|
+
) -> tuple[list[Any], list[str]]:
|
|
474
|
+
"""Handles and labels of both axes, in declared order."""
|
|
475
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
476
|
+
if ax2 is not None:
|
|
477
|
+
more_handles, more_labels = ax2.get_legend_handles_labels()
|
|
478
|
+
handles, labels = handles + more_handles, labels + more_labels
|
|
479
|
+
order = [*self.bars.values(), *self.lines.values()]
|
|
480
|
+
pairs = sorted(
|
|
481
|
+
zip(handles, labels, strict=True),
|
|
482
|
+
key=lambda pair: order.index(pair[1]),
|
|
483
|
+
)
|
|
484
|
+
return [h for h, _ in pairs], [label for _, label in pairs]
|
|
485
|
+
|
|
354
486
|
def save(
|
|
355
487
|
self,
|
|
356
488
|
fig: Figure,
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/plotly_backend.py
RENAMED
|
@@ -197,10 +197,11 @@ def _legend_side(fig: go.Figure, legend: Legend | None) -> None:
|
|
|
197
197
|
)
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
def _baseline(fig: go.Figure, reference: float) -> None:
|
|
201
|
-
"""Draw the house baseline (axhline) at *reference*."""
|
|
200
|
+
def _baseline(fig: go.Figure, reference: float, *, yref: str = "y") -> None:
|
|
201
|
+
"""Draw the house baseline (axhline) at *reference* on axis *yref*."""
|
|
202
202
|
fig.add_hline(
|
|
203
203
|
y=reference,
|
|
204
|
+
yref=yref,
|
|
204
205
|
line_color=AX_CONFIG["baseline"]["color"],
|
|
205
206
|
line_width=AX_CONFIG["baseline"]["linewidth"],
|
|
206
207
|
)
|
|
@@ -503,7 +504,8 @@ def bar_line_to_figure(chart: BarLinePlot) -> go.Figure:
|
|
|
503
504
|
"""Interactive ``Figure`` from a :class:`BarLinePlot`.
|
|
504
505
|
|
|
505
506
|
Bars (grouped) and marked lines share one y-axis over a categorical
|
|
506
|
-
x-axis of the same date-formatted labels the matplotlib artist uses
|
|
507
|
+
x-axis of the same date-formatted labels the matplotlib artist uses;
|
|
508
|
+
the ``secondary`` series go to an overlaid right-hand ``yaxis2``.
|
|
507
509
|
"""
|
|
508
510
|
plot_data = chart._prepare_data()
|
|
509
511
|
palette = _house_palette()
|
|
@@ -515,6 +517,9 @@ def bar_line_to_figure(chart: BarLinePlot) -> go.Figure:
|
|
|
515
517
|
default_alpha = float(BAR_LINE_CONFIG["bar"].get("alpha", 0.9))
|
|
516
518
|
line_cfg = BAR_LINE_CONFIG["line"]
|
|
517
519
|
|
|
520
|
+
def yaxis(col: str) -> str:
|
|
521
|
+
return "y2" if col in chart.secondary else "y"
|
|
522
|
+
|
|
518
523
|
for j, (col, label) in enumerate(chart.bars.items()):
|
|
519
524
|
style = chart.bar_styles.get(col, {})
|
|
520
525
|
color = (
|
|
@@ -525,6 +530,7 @@ def bar_line_to_figure(chart: BarLinePlot) -> go.Figure:
|
|
|
525
530
|
x=x,
|
|
526
531
|
y=plot_data[col].fillna(0),
|
|
527
532
|
name=label,
|
|
533
|
+
yaxis=yaxis(col),
|
|
528
534
|
marker_color=_color(color, style.get("alpha", default_alpha)),
|
|
529
535
|
)
|
|
530
536
|
)
|
|
@@ -541,6 +547,7 @@ def bar_line_to_figure(chart: BarLinePlot) -> go.Figure:
|
|
|
541
547
|
y=plot_data[col],
|
|
542
548
|
mode="lines+markers",
|
|
543
549
|
name=label,
|
|
550
|
+
yaxis=yaxis(col),
|
|
544
551
|
line={
|
|
545
552
|
"color": _color(color),
|
|
546
553
|
"width": style.get(
|
|
@@ -552,10 +559,27 @@ def bar_line_to_figure(chart: BarLinePlot) -> go.Figure:
|
|
|
552
559
|
)
|
|
553
560
|
|
|
554
561
|
fig.update_layout(barmode="group")
|
|
562
|
+
if chart.ylabel:
|
|
563
|
+
fig.update_layout(yaxis_title_text=chart.ylabel)
|
|
564
|
+
if chart.secondary:
|
|
565
|
+
fig.update_layout(
|
|
566
|
+
yaxis_side="left",
|
|
567
|
+
yaxis2={
|
|
568
|
+
"overlaying": "y",
|
|
569
|
+
"side": "right",
|
|
570
|
+
"showgrid": False,
|
|
571
|
+
"ticksuffix": chart.secondary_fmt.units,
|
|
572
|
+
"title_text": chart.secondary_ylabel,
|
|
573
|
+
},
|
|
574
|
+
)
|
|
555
575
|
if chart.baseline:
|
|
556
|
-
|
|
576
|
+
on_primary, on_secondary = chart._baseline_on()
|
|
577
|
+
if on_primary:
|
|
578
|
+
_baseline(fig, 0)
|
|
579
|
+
if on_secondary:
|
|
580
|
+
_baseline(fig, 0, yref="y2")
|
|
557
581
|
if chart.fmt.units:
|
|
558
|
-
fig.
|
|
582
|
+
fig.update_layout(yaxis_ticksuffix=chart.fmt.units)
|
|
559
583
|
_legend_side(fig, chart.legend)
|
|
560
584
|
return fig
|
|
561
585
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/intraday_plot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/artists/vector_plot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/fonts/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/assets/tesoro.mplstyle
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/data_sources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/dependencies/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/dependencies/resolution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/providers/imf_irfcl.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/content.py
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/images.py
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/section.py
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/subtitle.py
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/table.py
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/text.py
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/render/content/title.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/Dockerfile.tmpl
RENAMED
|
File without changes
|
{tesorotools_python-0.1.2 → tesorotools_python-0.1.3}/src/tesorotools/web/templates/README.md.tmpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|