tesorotools-python 0.0.51__tar.gz → 0.0.52__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.51 → tesorotools_python-0.0.52}/.gitignore +5 -1
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/PKG-INFO +1 -1
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/_common.py +237 -31
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/line_plot.py +126 -10
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/plotly_backend.py +6 -2
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/plots.yaml +22 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/pyproject.toml +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/_build_context.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/_registry.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/bar_line.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/box_plot.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/compact.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/intraday_plot.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/matrix.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/shock_plot.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/stacked.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/type_curve.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/vector_plot.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/waterfall.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/template.docx +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/driver.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/orchestration.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/imf_irfcl.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/lseg.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/table.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/document.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/utils/template.py +0 -0
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
__pycache__
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# Ignorar ajustes personales de VS Code, pero versionar los compartidos
|
|
7
|
+
# (recomendación de extensiones y formato de markdown con prettier).
|
|
8
|
+
.vscode/*
|
|
9
|
+
!.vscode/extensions.json
|
|
10
|
+
!.vscode/settings.json
|
|
7
11
|
|
|
8
12
|
data/
|
|
9
13
|
config/
|
|
@@ -24,7 +24,7 @@ overrides only need to update ``plots.yaml``.
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
26
|
from pathlib import Path
|
|
27
|
-
from typing import Any, Protocol, Self, cast, runtime_checkable
|
|
27
|
+
from typing import Any, Literal, Protocol, Self, cast, runtime_checkable
|
|
28
28
|
|
|
29
29
|
import matplotlib.pyplot as plt
|
|
30
30
|
import pandas as pd
|
|
@@ -58,10 +58,44 @@ apply_house_style()
|
|
|
58
58
|
AX_CONFIG: dict[str, Any] = PLOT_CONFIG["ax"]
|
|
59
59
|
FIG_CONFIG: dict[str, Any] = PLOT_CONFIG["figure"]
|
|
60
60
|
COMPACT_CONFIG: dict[str, Any] = PLOT_CONFIG.get("compact", {})
|
|
61
|
+
LINE_CONFIG: dict[str, Any] = PLOT_CONFIG.get("line", {})
|
|
61
62
|
SAVE_CONFIG: dict[str, Any] = PLOT_CONFIG.get("save", {})
|
|
62
63
|
|
|
63
64
|
CM_TO_IN: float = 1.0 / 2.54
|
|
64
65
|
|
|
66
|
+
# Soft date-density levels -> AutoDateLocator maxticks. Fallbacks used when the
|
|
67
|
+
# matching ``plots.yaml`` block (``compact.date_maxticks`` / ``line.date_maxticks``)
|
|
68
|
+
# is missing a level; "default" reproduces the historical tick count of each mode
|
|
69
|
+
# (4 for compact, 6 for non-compact).
|
|
70
|
+
DATE_DENSITY_LEVELS: tuple[str, ...] = ("few", "default", "many")
|
|
71
|
+
_DATE_MAXTICKS_FALLBACK_COMPACT: dict[str, int] = {
|
|
72
|
+
"few": 3,
|
|
73
|
+
"default": 4,
|
|
74
|
+
"many": 7,
|
|
75
|
+
}
|
|
76
|
+
_DATE_MAXTICKS_FALLBACK_LINE: dict[str, int] = {
|
|
77
|
+
"few": 3,
|
|
78
|
+
"default": 6,
|
|
79
|
+
"many": 10,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def date_maxticks(density: str, *, compact: bool) -> int:
|
|
84
|
+
"""Resolve a soft ``date_density`` level to an ``AutoDateLocator`` maxticks.
|
|
85
|
+
|
|
86
|
+
Reads the per-mode ``date_maxticks`` table from ``plots.yaml``
|
|
87
|
+
(``compact.date_maxticks`` when *compact*, else ``line.date_maxticks``),
|
|
88
|
+
falling back to the built-in table when the config (or a level) is absent.
|
|
89
|
+
"""
|
|
90
|
+
cfg = COMPACT_CONFIG if compact else LINE_CONFIG
|
|
91
|
+
fallback = (
|
|
92
|
+
_DATE_MAXTICKS_FALLBACK_COMPACT
|
|
93
|
+
if compact
|
|
94
|
+
else _DATE_MAXTICKS_FALLBACK_LINE
|
|
95
|
+
)
|
|
96
|
+
table: dict[str, Any] = cfg.get("date_maxticks", fallback)
|
|
97
|
+
return int(table.get(density, fallback[density]))
|
|
98
|
+
|
|
65
99
|
|
|
66
100
|
def dynamic_dpi(fig: Figure) -> int:
|
|
67
101
|
"""Save DPI adapted to the figure's physical size.
|
|
@@ -258,6 +292,51 @@ def adjust_figure_for_plot_size(
|
|
|
258
292
|
fig.set_size_inches(new_w, new_h)
|
|
259
293
|
|
|
260
294
|
|
|
295
|
+
def grow_height_to_axes(
|
|
296
|
+
fig: Figure,
|
|
297
|
+
ax: Axes,
|
|
298
|
+
target_axes_h_in: float,
|
|
299
|
+
*,
|
|
300
|
+
max_iter: int = 3,
|
|
301
|
+
cap_factor: float = 2.0,
|
|
302
|
+
) -> None:
|
|
303
|
+
"""Grow the figure **height** (width fixed) until *ax* is *target* inches tall.
|
|
304
|
+
|
|
305
|
+
Under ``constrained`` layout an outside legend band and the x-tick label
|
|
306
|
+
strip keep a roughly **fixed absolute** height, so height added to the
|
|
307
|
+
figure flows almost entirely into the axes rectangle. Growing to the axes
|
|
308
|
+
height measured *before* the legend was added therefore gives the plot area
|
|
309
|
+
back the vertical space the legend (and any rotated date labels) stole,
|
|
310
|
+
instead of squashing it inside a fixed figure.
|
|
311
|
+
|
|
312
|
+
A feedback loop (grow -> draw -> re-measure the deficit) converges in a few
|
|
313
|
+
passes because the reserved bands are non-linear only at the margin; total
|
|
314
|
+
growth is capped at ``cap_factor`` times the original height so a huge
|
|
315
|
+
legend cannot run the figure away. Measures the axes rectangle via
|
|
316
|
+
``get_window_extent`` (not ``get_tightbbox``, which includes the very tick
|
|
317
|
+
strip being budgeted for).
|
|
318
|
+
"""
|
|
319
|
+
orig_w, orig_h = (float(v) for v in fig.get_size_inches())
|
|
320
|
+
max_h = orig_h * cap_factor
|
|
321
|
+
eps_in = 0.02
|
|
322
|
+
for _ in range(max_iter):
|
|
323
|
+
fig.canvas.draw() # type: ignore[reportUnknownMemberType]
|
|
324
|
+
renderer = fig.canvas.get_renderer() # type: ignore[reportUnknownMemberType]
|
|
325
|
+
ax_h_in = (
|
|
326
|
+
ax.get_window_extent(renderer).height # type: ignore[reportUnknownArgumentType]
|
|
327
|
+
/ fig.dpi
|
|
328
|
+
)
|
|
329
|
+
deficit = target_axes_h_in - ax_h_in
|
|
330
|
+
if deficit <= eps_in:
|
|
331
|
+
break
|
|
332
|
+
current_h = float(fig.get_size_inches()[1])
|
|
333
|
+
new_h = min(max_h, current_h + deficit)
|
|
334
|
+
if new_h <= current_h: # capped out: no further progress possible
|
|
335
|
+
break
|
|
336
|
+
fig.set_size_inches(orig_w, new_h)
|
|
337
|
+
fig.canvas.draw() # type: ignore[reportUnknownMemberType]
|
|
338
|
+
|
|
339
|
+
|
|
261
340
|
def auto_ncol(
|
|
262
341
|
ax: Axes,
|
|
263
342
|
labels: list[str],
|
|
@@ -496,25 +575,62 @@ class _StartLabeledConcise(ConciseDateFormatter):
|
|
|
496
575
|
return labels
|
|
497
576
|
|
|
498
577
|
|
|
578
|
+
def _date_label_ha(
|
|
579
|
+
rotation: float | None,
|
|
580
|
+
) -> Literal["left", "center", "right"]:
|
|
581
|
+
"""Horizontal alignment for date tick labels at *rotation* degrees.
|
|
582
|
+
|
|
583
|
+
Horizontal labels centre under their tick; a positive (counter-clockwise,
|
|
584
|
+
up-to-the-right) slant reads best right-anchored so each label runs
|
|
585
|
+
down-left under its tick, and a negative slant left-anchored.
|
|
586
|
+
"""
|
|
587
|
+
if rotation is None or rotation == 0:
|
|
588
|
+
return "center"
|
|
589
|
+
return "right" if rotation > 0 else "left"
|
|
590
|
+
|
|
591
|
+
|
|
499
592
|
def compact_date_axis(
|
|
500
593
|
ax: Axes,
|
|
501
594
|
data_min: Any,
|
|
502
595
|
data_max: Any,
|
|
503
596
|
*,
|
|
504
597
|
maxticks: int = 4,
|
|
598
|
+
rotation: float | None = None,
|
|
599
|
+
pin_start: bool | None = None,
|
|
600
|
+
auto_rotate: bool = False,
|
|
505
601
|
) -> None:
|
|
506
|
-
"""Compact date x-axis: few
|
|
602
|
+
"""Compact date x-axis: few ticks, anchored to the data span.
|
|
507
603
|
|
|
508
604
|
Ticks are computed over the **data span** ``[data_min,
|
|
509
605
|
data_max]`` and pinned with a ``FixedLocator``, so none
|
|
510
606
|
float in the right-hand margin that the value labels open
|
|
511
607
|
up -- the last tick lands as close to the final data
|
|
512
|
-
point as the tick step allows. When
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
608
|
+
point as the tick step allows. When the start is pinned (see
|
|
609
|
+
``pin_start``) the first data point becomes the leading tick, so
|
|
610
|
+
the reader sees exactly when the series begins. Requires the
|
|
611
|
+
series to have been plotted with ``x_compat=True`` (pandas' own
|
|
612
|
+
date plotting ignores this formatter).
|
|
613
|
+
|
|
614
|
+
``rotation`` (degrees) tilts the tick labels so more dates fit;
|
|
615
|
+
``None`` keeps them horizontal (the historical behaviour). The
|
|
616
|
+
rotation and its matching horizontal alignment are applied
|
|
617
|
+
**before** the a-priori de-overlap measures the label boxes, so
|
|
618
|
+
the collision sweep sees the real (slanted, anchored) geometry.
|
|
619
|
+
|
|
620
|
+
``pin_start`` pins the first data point as the leading tick;
|
|
621
|
+
``None`` (the default) reads ``compact.date_show_start``. Pass
|
|
622
|
+
``False`` to prefer a **clean equally spaced calendar grid** over
|
|
623
|
+
the (off-grid, wide) start label -- the caller does this when the
|
|
624
|
+
user asks for a non-default density or a rotation, where an
|
|
625
|
+
even cadence reads better than an anchored-but-ragged one.
|
|
626
|
+
|
|
627
|
+
``auto_rotate`` makes the requested density **actually count**: it
|
|
628
|
+
tries increasing tilts (0, 30, 45, 60, 90 degrees) and keeps the
|
|
629
|
+
smallest one at which the most (ideally all) of the requested date
|
|
630
|
+
ticks survive the de-overlap -- so choosing a denser level packs
|
|
631
|
+
more dates by leaning the labels only as far as needed, instead of
|
|
632
|
+
silently collapsing to the same few. Ignored when ``rotation`` is
|
|
633
|
+
given (an explicit angle wins).
|
|
518
634
|
"""
|
|
519
635
|
lo: float = float(cast(Any, date2num(data_min)))
|
|
520
636
|
hi: float = float(cast(Any, date2num(data_max)))
|
|
@@ -522,8 +638,13 @@ def compact_date_axis(
|
|
|
522
638
|
candidates: list[float] = cast(
|
|
523
639
|
"list[float]", locator.tick_values(data_min, data_max)
|
|
524
640
|
)
|
|
641
|
+
show_start: bool = (
|
|
642
|
+
bool(COMPACT_CONFIG.get("date_show_start", True))
|
|
643
|
+
if pin_start is None
|
|
644
|
+
else pin_start
|
|
645
|
+
)
|
|
525
646
|
interior: list[float] = [t for t in candidates if lo < t <= hi]
|
|
526
|
-
if
|
|
647
|
+
if show_start:
|
|
527
648
|
# pin the first data point as the leading tick so the reader sees
|
|
528
649
|
# exactly when the series starts (the natural calendar tick often
|
|
529
650
|
# falls just *before* the first datum and is clipped by margins=0).
|
|
@@ -533,7 +654,6 @@ def compact_date_axis(
|
|
|
533
654
|
ticks: list[float] = [lo, *interior]
|
|
534
655
|
else:
|
|
535
656
|
ticks = [t for t in candidates if lo <= t <= hi] or [lo, hi]
|
|
536
|
-
show_start = bool(COMPACT_CONFIG.get("date_show_start", True))
|
|
537
657
|
|
|
538
658
|
def _set_ticks(values: list[float]) -> None:
|
|
539
659
|
loc = FixedLocator(values)
|
|
@@ -544,18 +664,34 @@ def compact_date_axis(
|
|
|
544
664
|
else ConciseDateFormatter(loc)
|
|
545
665
|
)
|
|
546
666
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
667
|
+
def _style_labels(rot: float) -> None:
|
|
668
|
+
# rotation goes in the axis tick kwargs; ha / anchor rotation-mode are
|
|
669
|
+
# per-label Text properties reapplied after every draw or locator swap.
|
|
670
|
+
ha = _date_label_ha(None if rot == 0 else rot)
|
|
671
|
+
ax.tick_params( # type: ignore[reportUnknownMemberType]
|
|
672
|
+
axis="x", rotation=rot
|
|
673
|
+
)
|
|
674
|
+
for label in ax.get_xticklabels():
|
|
675
|
+
label.set_horizontalalignment(ha)
|
|
676
|
+
if rot != 0:
|
|
677
|
+
label.set_rotation_mode("anchor")
|
|
678
|
+
|
|
679
|
+
def _decimate_at(rot: float) -> list[int]:
|
|
680
|
+
# Render all candidate ticks at this tilt, measure their label boxes,
|
|
681
|
+
# and thin to a UNIFORMLY spaced subset when they collide -- so the
|
|
682
|
+
# survivors stay equally spaced in time (a greedy left-to-right drop
|
|
683
|
+
# leaves ragged gaps). Returns the kept indices into ``ticks``.
|
|
684
|
+
_set_ticks(ticks)
|
|
685
|
+
chosen: list[int] = list(range(len(ticks)))
|
|
686
|
+
fig = ax.get_figure()
|
|
687
|
+
if fig is None or len(ticks) <= 1:
|
|
688
|
+
_style_labels(rot)
|
|
689
|
+
return chosen
|
|
558
690
|
fig.canvas.draw() # type: ignore[reportUnknownMemberType]
|
|
691
|
+
# set ha AFTER the draw (labels now exist) but BEFORE measuring:
|
|
692
|
+
# get_window_extent recomputes from the current Text properties, so the
|
|
693
|
+
# boxes reflect both the rotation (from the draw) and the anchor/ha.
|
|
694
|
+
_style_labels(rot)
|
|
559
695
|
renderer = fig.canvas.get_renderer() # type: ignore[reportUnknownMemberType]
|
|
560
696
|
boxes = [
|
|
561
697
|
lbl.get_window_extent(renderer) # type: ignore[reportUnknownArgumentType]
|
|
@@ -563,18 +699,88 @@ def compact_date_axis(
|
|
|
563
699
|
]
|
|
564
700
|
if len(boxes) == len(ticks):
|
|
565
701
|
pad_px = 2.0
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
702
|
+
n = len(ticks)
|
|
703
|
+
|
|
704
|
+
def _clears(idx: list[int]) -> bool:
|
|
705
|
+
return all(
|
|
706
|
+
boxes[b].x0 > boxes[a].x1 + pad_px
|
|
707
|
+
for a, b in zip(idx, idx[1:])
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
# decimate from the right (anchor the latest date) by a constant
|
|
711
|
+
# stride so the gaps are equal. Force-keep index 0 ONLY when it is
|
|
712
|
+
# the pinned start tick; otherwise the whole grid is uniform and
|
|
713
|
+
# forcing it would re-open a ragged left gap.
|
|
714
|
+
keep0: set[int] = {0} if show_start else set()
|
|
715
|
+
chosen = sorted(keep0 | {0, n - 1})
|
|
716
|
+
for stride in range(1, n):
|
|
717
|
+
idx = sorted(keep0 | set(range(n - 1, -1, -stride)))
|
|
718
|
+
if _clears(idx):
|
|
719
|
+
chosen = idx
|
|
720
|
+
break
|
|
721
|
+
return chosen
|
|
722
|
+
|
|
723
|
+
if auto_rotate and rotation is None:
|
|
724
|
+
# honour the requested density by leaning the labels only as far as
|
|
725
|
+
# needed: pick the smallest tilt keeping the most date ticks (ties ->
|
|
726
|
+
# flatter), stopping as soon as one keeps them all.
|
|
727
|
+
best_rot = 0.0
|
|
728
|
+
best_chosen = _decimate_at(0.0)
|
|
729
|
+
for cand in (30.0, 45.0, 60.0, 90.0):
|
|
730
|
+
if len(best_chosen) >= len(ticks):
|
|
731
|
+
break
|
|
732
|
+
cand_chosen = _decimate_at(cand)
|
|
733
|
+
if len(cand_chosen) > len(best_chosen):
|
|
734
|
+
best_rot, best_chosen = cand, cand_chosen
|
|
735
|
+
rot_final, chosen = best_rot, best_chosen
|
|
736
|
+
else:
|
|
737
|
+
rot_final = 0.0 if rotation is None else float(rotation)
|
|
738
|
+
chosen = _decimate_at(rot_final)
|
|
739
|
+
|
|
740
|
+
# apply the winning tick subset + tilt as the final state
|
|
741
|
+
if len(chosen) < len(ticks):
|
|
742
|
+
_set_ticks([ticks[i] for i in chosen])
|
|
743
|
+
fig = ax.get_figure()
|
|
744
|
+
if fig is not None:
|
|
745
|
+
fig.canvas.draw() # type: ignore[reportUnknownMemberType]
|
|
746
|
+
_style_labels(rot_final)
|
|
747
|
+
|
|
575
748
|
|
|
749
|
+
def apply_date_axis(
|
|
750
|
+
ax: Axes,
|
|
751
|
+
*,
|
|
752
|
+
maxticks: int,
|
|
753
|
+
rotation: float | None = None,
|
|
754
|
+
) -> None:
|
|
755
|
+
"""Native matplotlib date x-axis with a soft tick count and optional slant.
|
|
756
|
+
|
|
757
|
+
The non-compact counterpart to :func:`compact_date_axis`: it takes over the
|
|
758
|
+
x-axis (which must carry a **native** date axis, i.e. the series were
|
|
759
|
+
plotted with ``x_compat=True``) with an ``AutoDateLocator`` capped at
|
|
760
|
+
*maxticks* and a ``ConciseDateFormatter`` bound to the **same** locator
|
|
761
|
+
instance (the concise context logic misfires with a mismatched one).
|
|
762
|
+
``rotation`` (degrees) tilts the labels; ``None`` keeps them horizontal.
|
|
763
|
+
|
|
764
|
+
Unlike the compact path this changes the label **text style** too (concise
|
|
765
|
+
hierarchical labels instead of pandas' own date formatting), so callers use
|
|
766
|
+
it only when the user explicitly asks for a non-default density or rotation.
|
|
767
|
+
"""
|
|
768
|
+
locator = AutoDateLocator(minticks=2, maxticks=maxticks)
|
|
769
|
+
ax.xaxis.set_major_locator(locator)
|
|
770
|
+
ax.xaxis.set_major_formatter(ConciseDateFormatter(locator))
|
|
771
|
+
if rotation is None or rotation == 0:
|
|
772
|
+
return
|
|
773
|
+
rot = float(rotation)
|
|
774
|
+
ha = _date_label_ha(rotation)
|
|
775
|
+
ax.tick_params( # type: ignore[reportUnknownMemberType]
|
|
776
|
+
axis="x", rotation=rot
|
|
777
|
+
)
|
|
778
|
+
fig = ax.get_figure()
|
|
779
|
+
if fig is not None:
|
|
780
|
+
fig.canvas.draw() # type: ignore[reportUnknownMemberType]
|
|
576
781
|
for label in ax.get_xticklabels():
|
|
577
|
-
label.set_horizontalalignment(
|
|
782
|
+
label.set_horizontalalignment(ha)
|
|
783
|
+
label.set_rotation_mode("anchor")
|
|
578
784
|
|
|
579
785
|
|
|
580
786
|
def compact_y_axis(ax: Axes) -> None:
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/line_plot.py
RENAMED
|
@@ -23,18 +23,22 @@ from yaml.nodes import MappingNode
|
|
|
23
23
|
|
|
24
24
|
from tesorotools.artists._common import (
|
|
25
25
|
AX_CONFIG,
|
|
26
|
+
DATE_DENSITY_LEVELS,
|
|
26
27
|
Format,
|
|
27
28
|
Legend,
|
|
28
29
|
resolve_data,
|
|
29
30
|
adjust_figure_for_plot_size,
|
|
30
31
|
annotate_last_values,
|
|
32
|
+
apply_date_axis,
|
|
31
33
|
compact_date_axis,
|
|
32
34
|
compact_figure_kwargs,
|
|
33
35
|
compact_font_pt,
|
|
34
36
|
compact_headroom_pad_pt,
|
|
35
37
|
compact_y_axis,
|
|
38
|
+
date_maxticks,
|
|
36
39
|
draw_vlines,
|
|
37
40
|
dynamic_dpi,
|
|
41
|
+
grow_height_to_axes,
|
|
38
42
|
place_legend,
|
|
39
43
|
style_baseline,
|
|
40
44
|
style_compact_axes,
|
|
@@ -108,6 +112,35 @@ class LinePlot:
|
|
|
108
112
|
with the axis at 2 (``1,14``) but the annotation at 4
|
|
109
113
|
(``1,1419``); a dollar-index chart with the axis at 0
|
|
110
114
|
(``101``) but the annotation at 1 (``101,1``).
|
|
115
|
+
* ``axis_units`` / ``annotate_units`` -- whether ``fmt.units``
|
|
116
|
+
is shown on the y-tick numbers and on the last-value
|
|
117
|
+
annotations, **independently**. Both default ``True`` (the
|
|
118
|
+
historical behaviour). Set ``axis_units: false`` to keep the
|
|
119
|
+
unit only on the value labels (a common house look: ``mM€``
|
|
120
|
+
on the annotations, bare numbers on the axis), or the reverse.
|
|
121
|
+
* ``date_density`` -- soft target for how many date ticks the
|
|
122
|
+
x-axis shows: ``"few"`` / ``"default"`` / ``"many"``, mapped
|
|
123
|
+
to a locator ``maxticks`` per mode in ``plots.yaml``
|
|
124
|
+
(``compact.date_maxticks`` / ``line.date_maxticks``).
|
|
125
|
+
``"default"`` reproduces the current tick count. The surviving
|
|
126
|
+
ticks are kept **equally spaced in time**. In compact mode a
|
|
127
|
+
denser level with no explicit ``date_rotation`` **auto-tilts**
|
|
128
|
+
the labels only as far as needed to actually fit the extra
|
|
129
|
+
dates (a narrow chart cannot fit many horizontal 4-digit years),
|
|
130
|
+
so choosing ``"many"`` reliably shows more.
|
|
131
|
+
* ``date_rotation`` -- tilt the date tick labels this many
|
|
132
|
+
degrees so more fit; ``None`` keeps them horizontal (and lets
|
|
133
|
+
the compact auto-tilt above pick an angle). A positive angle
|
|
134
|
+
slants up-to-the-right (right-anchored); setting it explicitly
|
|
135
|
+
disables the auto-tilt. In non-compact mode, setting a
|
|
136
|
+
non-default ``date_density`` or any ``date_rotation`` switches
|
|
137
|
+
the x-axis to matplotlib's concise date formatting (different
|
|
138
|
+
label *style* from pandas' default); leaving both at their
|
|
139
|
+
defaults keeps the pandas path.
|
|
140
|
+
* ``adapt_height`` -- when true (the default), grow the figure
|
|
141
|
+
**height** (keeping the width) so the plot area is not
|
|
142
|
+
squashed by a large legend or rotated date labels. Skipped
|
|
143
|
+
when ``plot_size`` is set (that already fixes the axes size).
|
|
111
144
|
|
|
112
145
|
Render lifecycle: ``build()`` returns ``(Figure, Axes)``
|
|
113
146
|
in memory; ``save(fig)`` persists to ``out_path``;
|
|
@@ -140,6 +173,11 @@ class LinePlot:
|
|
|
140
173
|
compact: bool = False,
|
|
141
174
|
font_pt: float | None = None,
|
|
142
175
|
axis_decimals: int | None = None,
|
|
176
|
+
axis_units: bool = True,
|
|
177
|
+
annotate_units: bool = True,
|
|
178
|
+
date_density: str = "default",
|
|
179
|
+
date_rotation: float | None = None,
|
|
180
|
+
adapt_height: bool = True,
|
|
143
181
|
ref_lines: list[dict[str, Any]] | None = None,
|
|
144
182
|
show_means: bool = False,
|
|
145
183
|
means_window: tuple[Any, Any] | None = None,
|
|
@@ -152,6 +190,11 @@ class LinePlot:
|
|
|
152
190
|
"either rebased multiplicatively (index) or subtractively "
|
|
153
191
|
"(change-since), not both"
|
|
154
192
|
)
|
|
193
|
+
if date_density not in DATE_DENSITY_LEVELS:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"date_density must be one of {DATE_DENSITY_LEVELS}: "
|
|
196
|
+
f"{date_density!r}"
|
|
197
|
+
)
|
|
155
198
|
self.out_path = out_path
|
|
156
199
|
self.data = resolve_data(data, data_path)
|
|
157
200
|
self.series = series
|
|
@@ -175,10 +218,27 @@ class LinePlot:
|
|
|
175
218
|
self.compact = compact
|
|
176
219
|
self.font_pt = font_pt
|
|
177
220
|
self.axis_decimals = axis_decimals
|
|
221
|
+
self.axis_units = axis_units
|
|
222
|
+
self.annotate_units = annotate_units
|
|
223
|
+
self.date_density = date_density
|
|
224
|
+
self.date_rotation = date_rotation
|
|
225
|
+
self.adapt_height = adapt_height
|
|
178
226
|
self.ref_lines = ref_lines or []
|
|
179
227
|
self.show_means = show_means
|
|
180
228
|
self.means_window = means_window
|
|
181
229
|
|
|
230
|
+
@property
|
|
231
|
+
def _custom_date_axis(self) -> bool:
|
|
232
|
+
"""Whether the non-compact chart takes over the x-axis for dates.
|
|
233
|
+
|
|
234
|
+
Only when the user asks for a non-default density or a rotation; else
|
|
235
|
+
the historical pandas date plotting is kept untouched. Compact mode has
|
|
236
|
+
its own date axis (:func:`compact_date_axis`) and never uses this path.
|
|
237
|
+
"""
|
|
238
|
+
return not self.compact and (
|
|
239
|
+
self.date_density != "default" or self.date_rotation is not None
|
|
240
|
+
)
|
|
241
|
+
|
|
182
242
|
@classmethod
|
|
183
243
|
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
184
244
|
"""Build a :class:`LinePlot` from the ``!line_plot`` YAML tag.
|
|
@@ -202,8 +262,9 @@ class LinePlot:
|
|
|
202
262
|
``baseline``, ``fmt`` (``!format``), ``legend``
|
|
203
263
|
(``!legend``), ``figsize``, ``series_styles``,
|
|
204
264
|
``plot_size``, ``vlines``, ``compact``, ``font_pt``,
|
|
205
|
-
``axis_decimals``, ``
|
|
206
|
-
``
|
|
265
|
+
``axis_decimals``, ``axis_units``, ``annotate_units``,
|
|
266
|
+
``date_density``, ``date_rotation``, ``adapt_height``,
|
|
267
|
+
``ref_lines``, ``show_means``, ``means_window``.
|
|
207
268
|
|
|
208
269
|
Example
|
|
209
270
|
-------
|
|
@@ -314,15 +375,17 @@ class LinePlot:
|
|
|
314
375
|
font_pt = compact_font_pt(self.font_pt) if self.compact else None
|
|
315
376
|
|
|
316
377
|
ax = fig.add_subplot()
|
|
378
|
+
# compact mode -- and the non-compact custom date axis -- need
|
|
379
|
+
# matplotlib's native date axis so the AutoDateLocator /
|
|
380
|
+
# ConciseDateFormatter take effect (x_compat disables pandas' own date
|
|
381
|
+
# plotting machinery). The default non-compact path keeps x_compat off.
|
|
382
|
+
native_date_axis = self.compact or self._custom_date_axis
|
|
317
383
|
for col in plot_data.columns:
|
|
318
384
|
style = self.series_styles.get(col, {})
|
|
319
|
-
# compact mode needs matplotlib's native date axis so the
|
|
320
|
-
# ConciseDateFormatter takes effect (x_compat disables pandas'
|
|
321
|
-
# own date plotting machinery).
|
|
322
385
|
plot_data[col].plot(
|
|
323
386
|
ax=ax,
|
|
324
387
|
label=self.series[col],
|
|
325
|
-
x_compat=
|
|
388
|
+
x_compat=native_date_axis,
|
|
326
389
|
**style,
|
|
327
390
|
)
|
|
328
391
|
|
|
@@ -334,12 +397,17 @@ class LinePlot:
|
|
|
334
397
|
if self.vlines:
|
|
335
398
|
draw_vlines(ax, self.vlines)
|
|
336
399
|
|
|
400
|
+
# units are shown on the y-tick numbers and on the last-value
|
|
401
|
+
# annotations independently; compute each once here.
|
|
402
|
+
axis_units = self.fmt.units if self.axis_units else ""
|
|
403
|
+
annotate_units = self.fmt.units if self.annotate_units else ""
|
|
404
|
+
|
|
337
405
|
if self.annotate:
|
|
338
406
|
annotate_last_values(
|
|
339
407
|
ax,
|
|
340
408
|
plot_data,
|
|
341
409
|
decimals=self.fmt.decimals,
|
|
342
|
-
units=
|
|
410
|
+
units=annotate_units,
|
|
343
411
|
labels=self.series,
|
|
344
412
|
series_styles=self.series_styles,
|
|
345
413
|
annotate_color=self.annotate_color,
|
|
@@ -363,7 +431,7 @@ class LinePlot:
|
|
|
363
431
|
style_spines(
|
|
364
432
|
ax,
|
|
365
433
|
decimals=self.fmt.decimals,
|
|
366
|
-
units=
|
|
434
|
+
units=axis_units,
|
|
367
435
|
**AX_CONFIG["spines"],
|
|
368
436
|
)
|
|
369
437
|
# Decouple tick precision from annotation precision: style_spines
|
|
@@ -373,7 +441,7 @@ class LinePlot:
|
|
|
373
441
|
# the annotations at fmt.decimals.
|
|
374
442
|
if self.axis_decimals is not None:
|
|
375
443
|
axis_decimals = self.axis_decimals
|
|
376
|
-
units =
|
|
444
|
+
units = axis_units
|
|
377
445
|
|
|
378
446
|
def _fmt_axis_tick(y: float, _pos: int) -> str:
|
|
379
447
|
return format_annotation(y, axis_decimals, units)
|
|
@@ -425,11 +493,46 @@ class LinePlot:
|
|
|
425
493
|
zorder=1,
|
|
426
494
|
)
|
|
427
495
|
|
|
496
|
+
# Anchor for the height adaptation: the axes height BEFORE the date
|
|
497
|
+
# axis is thinned/rotated and BEFORE the legend is added, so growing
|
|
498
|
+
# back to it returns the vertical space that both the legend and any
|
|
499
|
+
# rotated date labels steal. Skipped when plot_size fixes the axes
|
|
500
|
+
# size itself. (The tiny tick-font difference vs the final compact
|
|
501
|
+
# styling is immaterial.)
|
|
502
|
+
target_axes_h_in: float | None = None
|
|
503
|
+
if self.adapt_height and self.plot_size is None:
|
|
504
|
+
fig.canvas.draw() # type: ignore[reportUnknownMemberType]
|
|
505
|
+
renderer = fig.canvas.get_renderer() # type: ignore[reportUnknownMemberType]
|
|
506
|
+
target_axes_h_in = (
|
|
507
|
+
ax.get_window_extent(renderer).height # type: ignore[reportUnknownArgumentType]
|
|
508
|
+
/ fig.dpi
|
|
509
|
+
)
|
|
510
|
+
|
|
428
511
|
if self.compact:
|
|
429
512
|
# pin the date ticks to the data span (after annotate widened the
|
|
430
513
|
# x-limit) so the last tick sits next to the final data point and
|
|
431
514
|
# none float in the value-label margin; match Word's body font.
|
|
432
|
-
|
|
515
|
+
# date_density -> maxticks; date_rotation tilts the labels.
|
|
516
|
+
# An explicit density or rotation asks for a specific cadence, so
|
|
517
|
+
# prefer a clean equally spaced calendar grid over the pinned
|
|
518
|
+
# (off-grid, wide) start label; otherwise keep the house default.
|
|
519
|
+
custom_dates = (
|
|
520
|
+
self.date_density != "default" or self.date_rotation is not None
|
|
521
|
+
)
|
|
522
|
+
compact_date_axis(
|
|
523
|
+
ax,
|
|
524
|
+
plot_data.index.min(),
|
|
525
|
+
plot_data.index.max(),
|
|
526
|
+
maxticks=date_maxticks(self.date_density, compact=True),
|
|
527
|
+
rotation=self.date_rotation,
|
|
528
|
+
pin_start=False if custom_dates else None,
|
|
529
|
+
# a denser level with no explicit angle leans the labels only
|
|
530
|
+
# as far as needed so the extra dates actually appear.
|
|
531
|
+
auto_rotate=(
|
|
532
|
+
self.date_density != "default"
|
|
533
|
+
and self.date_rotation is None
|
|
534
|
+
),
|
|
535
|
+
)
|
|
433
536
|
# y-axis side: keep the house RIGHT side (matching every other
|
|
434
537
|
# chart in a two-per-row row) by default, and move to the LEFT
|
|
435
538
|
# only when annotate is on -- there the right edge is needed for
|
|
@@ -440,6 +543,15 @@ class LinePlot:
|
|
|
440
543
|
# denser y-numbers (e.g. every 20 instead of every 50) so the
|
|
441
544
|
# small chart still has enough reference gridlines.
|
|
442
545
|
compact_y_axis(ax)
|
|
546
|
+
elif self._custom_date_axis:
|
|
547
|
+
# non-compact, but the user asked for a non-default density or a
|
|
548
|
+
# rotation: take over the (native) date axis with a concise
|
|
549
|
+
# formatter thinned to date_maxticks and tilt the labels.
|
|
550
|
+
apply_date_axis(
|
|
551
|
+
ax,
|
|
552
|
+
maxticks=date_maxticks(self.date_density, compact=False),
|
|
553
|
+
rotation=self.date_rotation,
|
|
554
|
+
)
|
|
443
555
|
|
|
444
556
|
if self.legend is not None:
|
|
445
557
|
# Draw from the actual labelled artists (so in-legend reference
|
|
@@ -454,6 +566,10 @@ class LinePlot:
|
|
|
454
566
|
# so the axes-sizing pass would fight it; skip it there.
|
|
455
567
|
if self.plot_size is not None and not self.compact:
|
|
456
568
|
adjust_figure_for_plot_size(fig, ax, self.plot_size)
|
|
569
|
+
elif target_axes_h_in is not None:
|
|
570
|
+
# keep the width, grow the height so the legend / rotated dates do
|
|
571
|
+
# not squash the plot area below its pre-legend height.
|
|
572
|
+
grow_height_to_axes(fig, ax, target_axes_h_in)
|
|
457
573
|
|
|
458
574
|
return fig, ax
|
|
459
575
|
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/plotly_backend.py
RENAMED
|
@@ -262,7 +262,9 @@ def line_plot_to_figure(chart: LinePlot) -> go.Figure:
|
|
|
262
262
|
x=series.index[-1],
|
|
263
263
|
y=float(series.iloc[-1]),
|
|
264
264
|
text=format_annotation(
|
|
265
|
-
float(series.iloc[-1]),
|
|
265
|
+
float(series.iloc[-1]),
|
|
266
|
+
chart.fmt.decimals,
|
|
267
|
+
chart.fmt.units if chart.annotate_units else "",
|
|
266
268
|
),
|
|
267
269
|
showarrow=False,
|
|
268
270
|
xanchor="left",
|
|
@@ -270,7 +272,9 @@ def line_plot_to_figure(chart: LinePlot) -> go.Figure:
|
|
|
270
272
|
font={"color": _color(color)},
|
|
271
273
|
)
|
|
272
274
|
|
|
273
|
-
|
|
275
|
+
# units on the y-tick numbers and on the annotations are independent
|
|
276
|
+
# toggles (mirrors the matplotlib artist).
|
|
277
|
+
if chart.fmt.units and chart.axis_units:
|
|
274
278
|
fig.update_yaxes(ticksuffix=chart.fmt.units)
|
|
275
279
|
return fig
|
|
276
280
|
|
|
@@ -89,6 +89,16 @@ waterfall:
|
|
|
89
89
|
|
|
90
90
|
line:
|
|
91
91
|
ncol: 5
|
|
92
|
+
# Densidad "soft" de fechas del eje X (no-compact): cada nivel de
|
|
93
|
+
# date_density se traduce en un maxticks para el AutoDateLocator (que es
|
|
94
|
+
# conservador: elige el intervalo bonito mas fino cuyo nº de ticks <=
|
|
95
|
+
# maxticks, asi que "many" va holgado). Solo se aplica cuando date_density
|
|
96
|
+
# != "default" o hay date_rotation (si no, se mantiene el ploteo de fechas
|
|
97
|
+
# de pandas por defecto).
|
|
98
|
+
date_maxticks:
|
|
99
|
+
few: 3
|
|
100
|
+
default: 6
|
|
101
|
+
many: 14
|
|
92
102
|
|
|
93
103
|
# Combo barras + línea (!bar_line): la línea es la serie protagonista
|
|
94
104
|
# (marcador circular por defecto, configurable por serie con line_styles),
|
|
@@ -150,6 +160,18 @@ compact:
|
|
|
150
160
|
annotate_headroom_pad_pt: 4 # hueco por encima de la etiqueta de valor más alta
|
|
151
161
|
date_show_start: true # fija la fecha del primer dato como primer tick X
|
|
152
162
|
y_max_ticks: 10 # nº máx. de números en el eje Y (más referencias)
|
|
163
|
+
# Densidad "soft" de fechas del eje X: cada nivel de date_density se traduce
|
|
164
|
+
# en el maxticks del locator de fechas. El nivel "default" (4) es el
|
|
165
|
+
# comportamiento actual. Nota: AutoDateLocator es conservador (elige el
|
|
166
|
+
# intervalo "bonito" mas fino cuyo nº de ticks <= maxticks contando los del
|
|
167
|
+
# borde), asi que "many" necesita un valor holgado para ofrecer intervalos
|
|
168
|
+
# finos; luego la de-solapamiento recorta a lo que de verdad cabe (mas aun
|
|
169
|
+
# con anotaciones de ultimo valor, que comprimen el eje). Con rotacion caben
|
|
170
|
+
# mas.
|
|
171
|
+
date_maxticks:
|
|
172
|
+
few: 3
|
|
173
|
+
default: 4
|
|
174
|
+
many: 12
|
|
153
175
|
# Gráficos de barras horizontales (barh / grouped_barh): el alto fijo de
|
|
154
176
|
# compact aplastaría las etiquetas de un gráfico con muchas categorías, así
|
|
155
177
|
# que en compact se mantiene el ANCHO fijo y el ALTO crece con el nº de
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/barh_plot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/intraday_plot.py
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/shock_plot.py
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/type_curve.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/vector_plot.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/artists/waterfall.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
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/fonts/README.md
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/template.docx
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/assets/tesoro.mplstyle
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/data_sources/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/data_sources/debug.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/database/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/dependencies/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/dependencies/node.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/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
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/pipeline/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/pipeline/diagnose.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/providers/imf_irfcl.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/content.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/images.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/section.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/subtitle.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/table.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/src/tesorotools/render/content/text.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.51 → tesorotools_python-0.0.52}/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
|