tesorotools-python 0.0.48__tar.gz → 0.0.50__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.50/.gitignore +35 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/PKG-INFO +1 -1
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/pyproject.toml +8 -2
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/__init__.py +15 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/__init__.py +2 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/_common.py +144 -11
- tesorotools_python-0.0.50/src/tesorotools/artists/barh_plot.py +926 -0
- tesorotools_python-0.0.50/src/tesorotools/artists/compact.py +66 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/line_plot.py +93 -70
- tesorotools_python-0.0.50/src/tesorotools/artists/shock_plot.py +333 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/stacked.py +48 -44
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/type_curve.py +69 -19
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/waterfall.py +6 -2
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/plots.yaml +18 -1
- tesorotools_python-0.0.50/src/tesorotools/assets/template.docx +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/__init__.py +2 -0
- tesorotools_python-0.0.50/src/tesorotools/render/document.py +32 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/report.py +7 -1
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/globals.py +5 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/matplotlib.py +8 -1
- tesorotools_python-0.0.48/.gitignore +0 -29
- tesorotools_python-0.0.48/src/tesorotools/artists/barh_plot.py +0 -579
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/_build_context.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/_registry.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/matrix.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/artists/vector_plot.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/driver.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/orchestration.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/providers/imf_irfcl.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/providers/lseg.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/table.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.0.48 → tesorotools_python-0.0.50}/src/tesorotools/utils/template.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
.build1/
|
|
3
|
+
|
|
4
|
+
__pycache__
|
|
5
|
+
|
|
6
|
+
.vscode/
|
|
7
|
+
|
|
8
|
+
data/
|
|
9
|
+
config/
|
|
10
|
+
debug/
|
|
11
|
+
tablas/
|
|
12
|
+
|
|
13
|
+
dist/
|
|
14
|
+
|
|
15
|
+
*.png
|
|
16
|
+
*.feather
|
|
17
|
+
*.csv
|
|
18
|
+
*.docx
|
|
19
|
+
*.docx#
|
|
20
|
+
# ...but the bundled base template must ship in the package/wheel.
|
|
21
|
+
!src/tesorotools/assets/template.docx
|
|
22
|
+
|
|
23
|
+
test/
|
|
24
|
+
|
|
25
|
+
# coverage
|
|
26
|
+
.coverage
|
|
27
|
+
|
|
28
|
+
# ruff
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
|
|
31
|
+
# uv cache (CI restores it into the repo root)
|
|
32
|
+
.uv-cache/
|
|
33
|
+
|
|
34
|
+
demo_compacto/
|
|
35
|
+
demo/
|
|
@@ -48,11 +48,17 @@ source = "vcs"
|
|
|
48
48
|
|
|
49
49
|
[tool.hatch.build.targets.wheel]
|
|
50
50
|
packages = ["src/tesorotools"]
|
|
51
|
-
artifacts = [
|
|
51
|
+
artifacts = [
|
|
52
|
+
"src/tesorotools/assets/fonts/*.otf",
|
|
53
|
+
"src/tesorotools/assets/template.docx",
|
|
54
|
+
]
|
|
52
55
|
|
|
53
56
|
[tool.hatch.build.targets.sdist]
|
|
54
57
|
include = ["src/tesorotools"]
|
|
55
|
-
artifacts = [
|
|
58
|
+
artifacts = [
|
|
59
|
+
"src/tesorotools/assets/fonts/*.otf",
|
|
60
|
+
"src/tesorotools/assets/template.docx",
|
|
61
|
+
]
|
|
56
62
|
|
|
57
63
|
[tool.coverage.run]
|
|
58
64
|
source = ["tesorotools"]
|
|
@@ -17,8 +17,17 @@ plural variants). See ``docs/extending.md`` for the
|
|
|
17
17
|
recommended idioms.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
20
21
|
from typing import TYPE_CHECKING, Any
|
|
21
22
|
|
|
23
|
+
try:
|
|
24
|
+
#: Installed distribution version (set by hatch-vcs from the git tag).
|
|
25
|
+
#: Falls back to ``"0.0.0"`` when the package is not installed (e.g.
|
|
26
|
+
#: running straight from a source checkout with no metadata).
|
|
27
|
+
__version__ = version("tesorotools-python")
|
|
28
|
+
except PackageNotFoundError: # pragma: no cover - source-only checkout
|
|
29
|
+
__version__ = "0.0.0"
|
|
30
|
+
|
|
22
31
|
if TYPE_CHECKING:
|
|
23
32
|
from tesorotools.providers.bde import BdeProvider
|
|
24
33
|
from tesorotools.providers.ecb import EcbProvider
|
|
@@ -50,6 +59,7 @@ from tesorotools.artists import (
|
|
|
50
59
|
Legend,
|
|
51
60
|
LinePlot,
|
|
52
61
|
MatrixChart,
|
|
62
|
+
ShockChart,
|
|
53
63
|
StackedAreaPlot,
|
|
54
64
|
StackedBarPlot,
|
|
55
65
|
TypeCurve,
|
|
@@ -72,6 +82,7 @@ from tesorotools.render import (
|
|
|
72
82
|
Table,
|
|
73
83
|
Text,
|
|
74
84
|
Title,
|
|
85
|
+
new_document,
|
|
75
86
|
)
|
|
76
87
|
|
|
77
88
|
|
|
@@ -84,6 +95,7 @@ def _register_builtins() -> None:
|
|
|
84
95
|
register_artist("type_curve", TypeCurve)
|
|
85
96
|
register_artist("vector_plot", VectorPlot)
|
|
86
97
|
register_artist("matrix", MatrixChart)
|
|
98
|
+
register_artist("shock_plot", ShockChart)
|
|
87
99
|
register_artist("waterfall", Waterfall)
|
|
88
100
|
|
|
89
101
|
register_tag("format", Format)
|
|
@@ -102,6 +114,7 @@ _register_builtins()
|
|
|
102
114
|
|
|
103
115
|
|
|
104
116
|
__all__ = [
|
|
117
|
+
"__version__",
|
|
105
118
|
"Artist",
|
|
106
119
|
"BdeProvider",
|
|
107
120
|
"BuildContext",
|
|
@@ -121,6 +134,7 @@ __all__ = [
|
|
|
121
134
|
"RegistryProtocol",
|
|
122
135
|
"Report",
|
|
123
136
|
"Section",
|
|
137
|
+
"ShockChart",
|
|
124
138
|
"StackedAreaPlot",
|
|
125
139
|
"StackedBarPlot",
|
|
126
140
|
"Subtitle",
|
|
@@ -141,6 +155,7 @@ __all__ = [
|
|
|
141
155
|
"iter_contexts",
|
|
142
156
|
"iter_providers",
|
|
143
157
|
"iter_tags",
|
|
158
|
+
"new_document",
|
|
144
159
|
"register_artist",
|
|
145
160
|
"register_artists",
|
|
146
161
|
"register_provider",
|
|
@@ -29,6 +29,7 @@ from tesorotools.artists._common import Artist, Format, Legend
|
|
|
29
29
|
from tesorotools.artists.barh_plot import GroupedBarChart, HorizontalBarChart
|
|
30
30
|
from tesorotools.artists.line_plot import LinePlot
|
|
31
31
|
from tesorotools.artists.matrix import MatrixChart
|
|
32
|
+
from tesorotools.artists.shock_plot import ShockChart
|
|
32
33
|
from tesorotools.artists.stacked import StackedAreaPlot, StackedBarPlot
|
|
33
34
|
from tesorotools.artists.type_curve import TypeCurve
|
|
34
35
|
from tesorotools.artists.vector_plot import VectorPlot
|
|
@@ -42,6 +43,7 @@ __all__ = [
|
|
|
42
43
|
"Legend",
|
|
43
44
|
"LinePlot",
|
|
44
45
|
"MatrixChart",
|
|
46
|
+
"ShockChart",
|
|
45
47
|
"StackedAreaPlot",
|
|
46
48
|
"StackedBarPlot",
|
|
47
49
|
"TypeCurve",
|
|
@@ -156,12 +156,20 @@ class Format:
|
|
|
156
156
|
|
|
157
157
|
Used by every artist as ``fmt`` and by tick formatters /
|
|
158
158
|
annotations. ``decimals`` is forwarded to ``round`` and
|
|
159
|
-
``units`` is appended to the formatted number.
|
|
159
|
+
``units`` is appended to the formatted number. ``signed``
|
|
160
|
+
prefixes a ``+`` to positive numbers (negatives already
|
|
161
|
+
carry ``-``); useful for a variation/change annotation so
|
|
162
|
+
its direction reads at a glance. It is opt-in and ignored
|
|
163
|
+
by axis-tick formatting (a row of ``+`` on the axis is
|
|
164
|
+
noise) -- set it on the ``annot_fmt`` that labels the bars.
|
|
160
165
|
"""
|
|
161
166
|
|
|
162
|
-
def __init__(
|
|
167
|
+
def __init__(
|
|
168
|
+
self, units: str = "", decimals: int = 0, signed: bool = False
|
|
169
|
+
) -> None:
|
|
163
170
|
self.units = units
|
|
164
171
|
self.decimals = decimals
|
|
172
|
+
self.signed = signed
|
|
165
173
|
|
|
166
174
|
@classmethod
|
|
167
175
|
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
@@ -381,7 +389,7 @@ def compact_figsize() -> tuple[float, float]:
|
|
|
381
389
|
return (width_cm * CM_TO_IN, height_cm * CM_TO_IN)
|
|
382
390
|
|
|
383
391
|
|
|
384
|
-
def compact_font_pt() -> float | None:
|
|
392
|
+
def compact_font_pt(override: float | None = None) -> float | None:
|
|
385
393
|
"""Font size (points) for compact charts, or ``None`` to keep the style.
|
|
386
394
|
|
|
387
395
|
Because the compact figure is rendered at its true
|
|
@@ -389,7 +397,17 @@ def compact_font_pt() -> float | None:
|
|
|
389
397
|
these points equal the points the reader sees in Word --
|
|
390
398
|
set ``compact.font_pt`` in ``plots.yaml`` to the document
|
|
391
399
|
body-text size so chart text matches running text.
|
|
400
|
+
|
|
401
|
+
``override`` lets a single chart pick its own size, taking
|
|
402
|
+
precedence over the config: a chart that sits inside a Word
|
|
403
|
+
table wants the **table** content size (often smaller than
|
|
404
|
+
body text), and a denser chart -- many long-named bars -- may
|
|
405
|
+
need a smaller size so the value labels and category names
|
|
406
|
+
stop colliding. ``None`` (the default) falls back to
|
|
407
|
+
``compact.font_pt``.
|
|
392
408
|
"""
|
|
409
|
+
if override is not None:
|
|
410
|
+
return float(override)
|
|
393
411
|
raw: Any = COMPACT_CONFIG.get("font_pt")
|
|
394
412
|
return None if raw is None else float(raw)
|
|
395
413
|
|
|
@@ -620,17 +638,132 @@ def legend_above(
|
|
|
620
638
|
)
|
|
621
639
|
|
|
622
640
|
|
|
623
|
-
|
|
624
|
-
|
|
641
|
+
# ----------------------------------------------------------------------
|
|
642
|
+
# Shared compact toolkit
|
|
643
|
+
# ----------------------------------------------------------------------
|
|
644
|
+
#
|
|
645
|
+
# The three helpers below pack the policy ``LinePlot`` / ``Waterfall``
|
|
646
|
+
# had inline so *every* image artist can opt into compact uniformly --
|
|
647
|
+
# fixed physical size, body-size ticks, the spine side and the
|
|
648
|
+
# above-the-axes legend -- instead of copy-pasting from this module's
|
|
649
|
+
# internals. They are re-exported publicly from
|
|
650
|
+
# :mod:`tesorotools.artists.compact`.
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def compact_figure_kwargs(
|
|
654
|
+
compact: bool, figsize: tuple[float, float] | None = None
|
|
655
|
+
) -> dict[str, Any]:
|
|
656
|
+
"""``FIG_CONFIG`` with the compact figure size wired in.
|
|
657
|
+
|
|
658
|
+
An explicit ``figsize`` always wins; otherwise a compact chart with no
|
|
659
|
+
``figsize`` gets :func:`compact_figsize` (the fixed 7.4 cm x 5.5 cm box
|
|
660
|
+
read from ``plots.yaml``). Mirrors the ``fig_kw`` block every artist
|
|
661
|
+
opened by hand.
|
|
662
|
+
"""
|
|
663
|
+
fig_kw = dict(FIG_CONFIG)
|
|
664
|
+
if figsize is not None:
|
|
665
|
+
fig_kw["figsize"] = figsize
|
|
666
|
+
elif compact:
|
|
667
|
+
fig_kw["figsize"] = compact_figsize()
|
|
668
|
+
return fig_kw
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def compact_barh_figsize(n_bars: int) -> tuple[float, float]:
|
|
672
|
+
"""Compact figure size for a horizontal-bar chart: width fixed, height grows.
|
|
673
|
+
|
|
674
|
+
The fixed compact height crushes the y-labels of a many-category barh,
|
|
675
|
+
so compact bar charts keep the compact **width** but scale **height**
|
|
676
|
+
with the bar count: ``margin + per_bar * n_bars`` centimetres
|
|
677
|
+
(``compact.barh_margin_cm`` / ``compact.barh_per_bar_cm``), floored at
|
|
678
|
+
the compact height so a one- or two-bar chart is not absurdly short.
|
|
679
|
+
"""
|
|
680
|
+
width_in, height_in = compact_figsize()
|
|
681
|
+
per_bar_cm: float = float(COMPACT_CONFIG.get("barh_per_bar_cm", 0.55))
|
|
682
|
+
margin_cm: float = float(COMPACT_CONFIG.get("barh_margin_cm", 1.6))
|
|
683
|
+
h_cm = margin_cm + per_bar_cm * max(1, n_bars)
|
|
684
|
+
return (width_in, max(height_in, h_cm * CM_TO_IN))
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def style_compact_axes(
|
|
688
|
+
ax: Axes, font_pt: float | None, *, side: str = "right"
|
|
689
|
+
) -> None:
|
|
690
|
+
"""Body-size ticks plus the compact spine policy for *ax*.
|
|
691
|
+
|
|
692
|
+
The stylesheet pins ``xtick.labelsize`` / ``ytick.labelsize`` to 12, so
|
|
693
|
+
setting ``font.size`` alone does **not** resize the ticks -- this calls
|
|
694
|
+
``tick_params(labelsize=font_pt)`` explicitly (when ``font_pt`` is set).
|
|
695
|
+
It then puts the y-ticks on ``side`` (``"right"`` -- the house side, the
|
|
696
|
+
default -- or ``"left"``, which frees the right edge for end-of-series
|
|
697
|
+
value labels) and drops the now-tickless top and opposite spines via
|
|
698
|
+
:func:`declutter_spines`.
|
|
699
|
+
"""
|
|
700
|
+
if font_pt is not None:
|
|
701
|
+
ax.tick_params( # type: ignore[reportUnknownMemberType]
|
|
702
|
+
axis="both", labelsize=font_pt
|
|
703
|
+
)
|
|
704
|
+
if side == "right":
|
|
705
|
+
ax.yaxis.tick_right()
|
|
706
|
+
else:
|
|
707
|
+
ax.yaxis.tick_left()
|
|
708
|
+
declutter_spines(ax, side=side)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def place_legend(
|
|
712
|
+
fig: Figure,
|
|
713
|
+
ax: Axes,
|
|
714
|
+
*,
|
|
715
|
+
compact: bool,
|
|
716
|
+
ncol: int | None = None,
|
|
717
|
+
) -> MplLegend | None:
|
|
718
|
+
"""Place the figure legend, compact-aware, from the labelled artists.
|
|
719
|
+
|
|
720
|
+
Compact charts get the band **above** the axes spanning the figure
|
|
721
|
+
width (:func:`legend_above`) with the compact legend metrics; ``ncol``
|
|
722
|
+
is auto-fitted to the full figure width and hard-capped by
|
|
723
|
+
:func:`compact_legend_max_ncol` unless the caller passes an explicit
|
|
724
|
+
``ncol``. Non-compact charts get the house ``outside lower center``
|
|
725
|
+
band. Returns ``None`` (drawing nothing) when there are no labels.
|
|
726
|
+
"""
|
|
727
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
728
|
+
if not labels:
|
|
729
|
+
return None
|
|
730
|
+
fig_w_px: float = fig.get_size_inches()[0] * fig.dpi
|
|
731
|
+
if compact:
|
|
732
|
+
legend_kw = compact_legend_kwargs()
|
|
733
|
+
if ncol is None:
|
|
734
|
+
ncol = auto_ncol(
|
|
735
|
+
ax, labels, available_width_px=fig_w_px, **legend_kw
|
|
736
|
+
)
|
|
737
|
+
max_ncol = compact_legend_max_ncol()
|
|
738
|
+
if max_ncol is not None:
|
|
739
|
+
ncol = min(ncol, max_ncol)
|
|
740
|
+
return legend_above(ax, handles, labels, ncol=ncol, **legend_kw)
|
|
741
|
+
if ncol is None:
|
|
742
|
+
ncol = auto_ncol(ax, labels, available_width_px=fig_w_px)
|
|
743
|
+
return fig.legend( # type: ignore[reportUnknownMemberType]
|
|
744
|
+
handles,
|
|
745
|
+
labels,
|
|
746
|
+
loc="outside lower center",
|
|
747
|
+
ncol=ncol,
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def declutter_spines(ax: Axes, side: str = "left") -> None:
|
|
752
|
+
"""Hide the spines that carry no ticks (top, plus the untickled side).
|
|
753
|
+
|
|
754
|
+
Compact charts keep x-ticks on the bottom and the y-ticks on a single
|
|
755
|
+
side, leaving the top spine and the *opposite* vertical spine as pure
|
|
756
|
+
clutter -- drop them to free space and reduce visual noise.
|
|
625
757
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
758
|
+
``side`` is the side the y-ticks live on: ``"left"`` (the historic
|
|
759
|
+
default, used by the annotating line chart and the bar/waterfall
|
|
760
|
+
charts) drops top+right; ``"right"`` (the house side every non-compact
|
|
761
|
+
chart keeps) drops top+left. The side became a choice with the compact
|
|
762
|
+
y-axis-side knob, so the dropped spine has to follow it -- otherwise a
|
|
763
|
+
right-ticked compact chart would lose the spine its ticks sit on.
|
|
631
764
|
"""
|
|
632
765
|
ax.spines["top"].set_visible(False)
|
|
633
|
-
ax.spines["right"].set_visible(False)
|
|
766
|
+
ax.spines["right" if side == "left" else "left"].set_visible(False)
|
|
634
767
|
|
|
635
768
|
|
|
636
769
|
# ----------------------------------------------------------------------
|