tesorotools-python 0.0.33__tar.gz → 0.0.35__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.33 → tesorotools_python-0.0.35}/PKG-INFO +1 -1
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/pyproject.toml +1 -1
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/line_plot.py +111 -43
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/stacked.py +25 -27
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/table.py +255 -225
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/type_curve.py +5 -3
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/plots.yaml +0 -2
- tesorotools_python-0.0.35/src/tesorotools/driver.py +138 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/images.py +159 -152
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/table.py +42 -7
- tesorotools_python-0.0.35/src/tesorotools/testing/__init__.py +5 -0
- tesorotools_python-0.0.35/src/tesorotools/testing/compare.py +147 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/format.py +21 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/.gitignore +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/barh.md +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/convert.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/main.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/utils/template.py +0 -0
{tesorotools_python-0.0.33 → tesorotools_python-0.0.35}/src/tesorotools/artists/line_plot.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import locale
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any, Self
|
|
4
|
+
from typing import Any, Self, cast
|
|
5
5
|
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
import pandas as pd
|
|
@@ -218,6 +218,27 @@ def annotate_last_values(
|
|
|
218
218
|
ax.set_xlim(xmin, xmax + (x1 - x0))
|
|
219
219
|
|
|
220
220
|
|
|
221
|
+
def draw_vlines(ax: Axes, vlines: list[dict[str, Any]]) -> None:
|
|
222
|
+
"""Draw labelled vertical event markers on *ax*.
|
|
223
|
+
|
|
224
|
+
Each entry must provide ``x`` (date-like). Optional keys
|
|
225
|
+
(``label``, ``color``, ``linestyle``, ``linewidth``, ...) are
|
|
226
|
+
forwarded to ``ax.axvline``. ``linestyle`` defaults to
|
|
227
|
+
``"dashed"`` so markers are visually distinct from data lines.
|
|
228
|
+
"""
|
|
229
|
+
for vline in vlines:
|
|
230
|
+
kwargs: dict[str, Any] = dict(vline)
|
|
231
|
+
x_raw: Any = kwargs.pop("x")
|
|
232
|
+
# matplotlib's stub types axvline's x as float, but at runtime
|
|
233
|
+
# it accepts any date-like recognised by the date converter.
|
|
234
|
+
x_dt = cast(Any, pd.to_datetime(x_raw)) # type: ignore[reportUnknownMemberType]
|
|
235
|
+
kwargs.setdefault("linestyle", "dashed")
|
|
236
|
+
ax.axvline( # type: ignore[reportUnknownMemberType]
|
|
237
|
+
x=x_dt,
|
|
238
|
+
**kwargs,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
221
242
|
def style_spines(
|
|
222
243
|
ax: Axes,
|
|
223
244
|
decimals: int,
|
|
@@ -289,21 +310,20 @@ def style_baseline(
|
|
|
289
310
|
reference: float = 0,
|
|
290
311
|
**baseline_config: Any,
|
|
291
312
|
) -> None:
|
|
292
|
-
|
|
313
|
+
"""Draw a horizontal baseline at *reference*.
|
|
314
|
+
|
|
315
|
+
Always uses ``axhline`` (with high zorder) so callers that
|
|
316
|
+
later restyle the spines do not silently erase the baseline.
|
|
317
|
+
"""
|
|
293
318
|
bottom_lim, top_lim = ax.get_ylim()
|
|
294
319
|
ax.set_ylim(
|
|
295
320
|
bottom=min(reference, bottom_lim),
|
|
296
321
|
top=max(reference, top_lim),
|
|
297
322
|
)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
ax.spines["top"].set_edgecolor(color)
|
|
303
|
-
else:
|
|
304
|
-
ax.axhline( # type: ignore[reportUnknownMemberType]
|
|
305
|
-
y=reference, **baseline_config
|
|
306
|
-
)
|
|
323
|
+
baseline_config.setdefault("zorder", 2.5)
|
|
324
|
+
ax.axhline( # type: ignore[reportUnknownMemberType]
|
|
325
|
+
y=reference, **baseline_config
|
|
326
|
+
)
|
|
307
327
|
|
|
308
328
|
|
|
309
329
|
def plot_line_chart(
|
|
@@ -312,12 +332,12 @@ def plot_line_chart(
|
|
|
312
332
|
*,
|
|
313
333
|
base_100: bool,
|
|
314
334
|
annotate: bool,
|
|
315
|
-
|
|
335
|
+
fmt: dict[str, Any],
|
|
316
336
|
**kwargs: Any,
|
|
317
337
|
) -> None:
|
|
318
338
|
if base_100:
|
|
319
339
|
data = data / data.iloc[0, :] * 100
|
|
320
|
-
if
|
|
340
|
+
if fmt["units"] == "p.b.":
|
|
321
341
|
data = data * 100
|
|
322
342
|
fig: Figure = plt.figure( # type: ignore[reportUnknownMemberType]
|
|
323
343
|
**FIG_CONFIG
|
|
@@ -328,11 +348,13 @@ def plot_line_chart(
|
|
|
328
348
|
pass
|
|
329
349
|
|
|
330
350
|
reference = 100 if base_100 else 0
|
|
331
|
-
style_spines(ax, **
|
|
351
|
+
style_spines(ax, **fmt, **AX_CONFIG["spines"])
|
|
332
352
|
style_baseline(ax, reference, **AX_CONFIG["baseline"])
|
|
333
|
-
ax.
|
|
334
|
-
|
|
335
|
-
|
|
353
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
354
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
355
|
+
handles,
|
|
356
|
+
label_strs,
|
|
357
|
+
loc="outside lower center",
|
|
336
358
|
ncol=(
|
|
337
359
|
kwargs["legend"]["ncol"]
|
|
338
360
|
if kwargs.get("legend", None) is not None
|
|
@@ -386,10 +408,8 @@ class Legend:
|
|
|
386
408
|
def __init__(
|
|
387
409
|
self,
|
|
388
410
|
ncol: int | None = None,
|
|
389
|
-
sep: float | None = None,
|
|
390
411
|
) -> None:
|
|
391
412
|
self.ncol = ncol
|
|
392
|
-
self.sep = sep
|
|
393
413
|
|
|
394
414
|
@classmethod
|
|
395
415
|
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
@@ -411,15 +431,17 @@ class LinePlot:
|
|
|
411
431
|
start_date: datetime.datetime | None = None,
|
|
412
432
|
end_date: datetime.datetime | None = None,
|
|
413
433
|
base_100: bool = False,
|
|
434
|
+
base_100_date: datetime.datetime | str | None = None,
|
|
414
435
|
annotate: bool = False,
|
|
415
436
|
annotate_color: str | None = None,
|
|
416
437
|
baseline: bool = False,
|
|
417
|
-
|
|
438
|
+
fmt: Format | None = None,
|
|
418
439
|
legend: Legend | None = None,
|
|
419
440
|
data: pd.DataFrame | None = None,
|
|
420
441
|
figsize: tuple[float, float] | None = None,
|
|
421
442
|
series_styles: dict[str, dict[str, Any]] | None = None,
|
|
422
443
|
plot_size: tuple[float, float] | None = None,
|
|
444
|
+
vlines: list[dict[str, Any]] | None = None,
|
|
423
445
|
) -> None:
|
|
424
446
|
|
|
425
447
|
if out_path.suffix != ".png":
|
|
@@ -443,9 +465,10 @@ class LinePlot:
|
|
|
443
465
|
raise ValueError("series is required")
|
|
444
466
|
|
|
445
467
|
self.base_100 = base_100
|
|
468
|
+
self.base_100_date = base_100_date
|
|
446
469
|
self.annotate = annotate
|
|
447
470
|
self.annotate_color = annotate_color
|
|
448
|
-
self.
|
|
471
|
+
self.fmt = fmt
|
|
449
472
|
self.start_date = start_date
|
|
450
473
|
self.end_date = end_date
|
|
451
474
|
self.series = series
|
|
@@ -455,6 +478,7 @@ class LinePlot:
|
|
|
455
478
|
self.figsize = figsize
|
|
456
479
|
self.series_styles = series_styles or {}
|
|
457
480
|
self.plot_size = plot_size
|
|
481
|
+
self.vlines = vlines or []
|
|
458
482
|
|
|
459
483
|
@classmethod
|
|
460
484
|
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
@@ -466,7 +490,13 @@ class LinePlot:
|
|
|
466
490
|
line_plot_cfg["data_path"] = Path(line_plot_cfg["data_path"])
|
|
467
491
|
return cls(**line_plot_cfg)
|
|
468
492
|
|
|
469
|
-
def
|
|
493
|
+
def build(self) -> tuple[Figure, Axes]:
|
|
494
|
+
"""Render the chart in memory without writing to disk.
|
|
495
|
+
|
|
496
|
+
Returns the ``(Figure, Axes)`` so callers can post-process
|
|
497
|
+
before saving (extra annotations, override DPI, embed in a
|
|
498
|
+
composite figure). Pair with :meth:`save` to persist.
|
|
499
|
+
"""
|
|
470
500
|
start_date: pd.Timestamp = (
|
|
471
501
|
self.data.index.min()
|
|
472
502
|
if self.start_date is None
|
|
@@ -485,8 +515,19 @@ class LinePlot:
|
|
|
485
515
|
|
|
486
516
|
plot_data = plot_data * self.scale
|
|
487
517
|
|
|
488
|
-
if self.base_100:
|
|
489
|
-
|
|
518
|
+
if self.base_100:
|
|
519
|
+
if self.base_100_date is None:
|
|
520
|
+
anchor: pd.Series[float] = plot_data.iloc[0, :]
|
|
521
|
+
else:
|
|
522
|
+
anchor_ts: pd.Timestamp = pd.to_datetime(self.base_100_date)
|
|
523
|
+
idx_pos: int = self.data.index.get_indexer(
|
|
524
|
+
pd.Index([anchor_ts]), method="nearest"
|
|
525
|
+
)[0]
|
|
526
|
+
anchor = (
|
|
527
|
+
self.data.iloc[idx_pos, :].loc[list(self.series.keys())]
|
|
528
|
+
* self.scale
|
|
529
|
+
)
|
|
530
|
+
plot_data = plot_data / anchor * 100
|
|
490
531
|
|
|
491
532
|
fig_kw = dict(FIG_CONFIG)
|
|
492
533
|
if self.figsize is not None:
|
|
@@ -500,13 +541,16 @@ class LinePlot:
|
|
|
500
541
|
style = styles.get(col, {}) if styles else {}
|
|
501
542
|
plot_data[col].plot(ax=ax, label=self.series[col], **style)
|
|
502
543
|
|
|
503
|
-
|
|
544
|
+
if self.vlines:
|
|
545
|
+
draw_vlines(ax, self.vlines)
|
|
546
|
+
|
|
547
|
+
assert self.fmt is not None
|
|
504
548
|
if self.annotate:
|
|
505
549
|
annotate_last_values(
|
|
506
550
|
ax,
|
|
507
551
|
plot_data,
|
|
508
|
-
decimals=self.
|
|
509
|
-
units=self.
|
|
552
|
+
decimals=self.fmt.decimals,
|
|
553
|
+
units=self.fmt.units,
|
|
510
554
|
labels=self.series,
|
|
511
555
|
series_styles=self.series_styles,
|
|
512
556
|
annotate_color=self.annotate_color,
|
|
@@ -514,8 +558,8 @@ class LinePlot:
|
|
|
514
558
|
|
|
515
559
|
style_spines( # maybe make this function accept a Format object
|
|
516
560
|
ax,
|
|
517
|
-
decimals=self.
|
|
518
|
-
units=self.
|
|
561
|
+
decimals=self.fmt.decimals,
|
|
562
|
+
units=self.fmt.units,
|
|
519
563
|
**AX_CONFIG["spines"],
|
|
520
564
|
)
|
|
521
565
|
if self.baseline:
|
|
@@ -524,30 +568,54 @@ class LinePlot:
|
|
|
524
568
|
|
|
525
569
|
if self.legend is not None:
|
|
526
570
|
labels = [self.series[c] for c in plot_data.columns]
|
|
571
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
572
|
+
fig_width_px: float = fig.get_size_inches()[0] * fig.dpi
|
|
527
573
|
ncol = (
|
|
528
574
|
self.legend.ncol
|
|
529
575
|
if self.legend.ncol is not None
|
|
530
|
-
else auto_ncol(ax, labels)
|
|
531
|
-
)
|
|
532
|
-
sep = (
|
|
533
|
-
self.legend.sep
|
|
534
|
-
if self.legend.sep is not None
|
|
535
|
-
else LINE_PLOT_CONFIG["legend_sep"]
|
|
576
|
+
else auto_ncol(ax, labels, available_width_px=fig_width_px)
|
|
536
577
|
)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
578
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
579
|
+
handles,
|
|
580
|
+
label_strs,
|
|
581
|
+
loc="outside lower center",
|
|
540
582
|
ncol=ncol,
|
|
541
583
|
)
|
|
542
|
-
else:
|
|
543
|
-
ax.legend().set_visible( # type: ignore[reportUnknownMemberType]
|
|
544
|
-
False
|
|
545
|
-
)
|
|
546
584
|
|
|
547
585
|
if self.plot_size is not None:
|
|
548
586
|
adjust_figure_for_plot_size(fig, ax, self.plot_size)
|
|
549
587
|
|
|
588
|
+
return fig, ax
|
|
589
|
+
|
|
590
|
+
def save(
|
|
591
|
+
self,
|
|
592
|
+
fig: Figure,
|
|
593
|
+
*,
|
|
594
|
+
path: Path | None = None,
|
|
595
|
+
dpi: int | None = None,
|
|
596
|
+
) -> Path:
|
|
597
|
+
"""Persist *fig* as a PNG. Returns the path written.
|
|
598
|
+
|
|
599
|
+
Defaults to ``self.out_path``; pass ``path`` to redirect or
|
|
600
|
+
``dpi`` to override the figure DPI for the saved file
|
|
601
|
+
(useful when embedding in Word at fixed widths, where the
|
|
602
|
+
default 500 dpi balloons file sizes).
|
|
603
|
+
"""
|
|
604
|
+
target: Path = path if path is not None else self.out_path
|
|
605
|
+
save_kwargs: dict[str, Any] = {}
|
|
606
|
+
if dpi is not None:
|
|
607
|
+
save_kwargs["dpi"] = dpi
|
|
550
608
|
fig.savefig( # type: ignore[reportUnknownMemberType]
|
|
551
|
-
|
|
609
|
+
target, **save_kwargs
|
|
552
610
|
)
|
|
611
|
+
return target
|
|
612
|
+
|
|
613
|
+
def plot(self) -> Axes:
|
|
614
|
+
"""Build the chart and persist it to ``self.out_path``.
|
|
615
|
+
|
|
616
|
+
Kept for backwards compatibility; new callers should prefer
|
|
617
|
+
:meth:`build` + :meth:`save` for finer control.
|
|
618
|
+
"""
|
|
619
|
+
fig, ax = self.build()
|
|
620
|
+
self.save(fig)
|
|
553
621
|
return ax
|
|
@@ -22,8 +22,6 @@ from tesorotools.artists.line_plot import (
|
|
|
22
22
|
)
|
|
23
23
|
from tesorotools.utils.config import TemplateLoader
|
|
24
24
|
|
|
25
|
-
_DEFAULT_SEP = -0.125
|
|
26
|
-
|
|
27
25
|
|
|
28
26
|
class StackedAreaPlot:
|
|
29
27
|
"""Stacked area chart with the tesorotools visual style.
|
|
@@ -43,7 +41,7 @@ class StackedAreaPlot:
|
|
|
43
41
|
start_date: str | None = None,
|
|
44
42
|
end_date: str | None = None,
|
|
45
43
|
baseline: bool = False,
|
|
46
|
-
|
|
44
|
+
fmt: Format | None = None,
|
|
47
45
|
legend: Legend | None = None,
|
|
48
46
|
figsize: tuple[float, float] | None = None,
|
|
49
47
|
plot_size: tuple[float, float] | None = None,
|
|
@@ -57,7 +55,7 @@ class StackedAreaPlot:
|
|
|
57
55
|
self.start_date = start_date
|
|
58
56
|
self.end_date = end_date
|
|
59
57
|
self.baseline = baseline
|
|
60
|
-
self.
|
|
58
|
+
self.fmt = fmt or Format()
|
|
61
59
|
self.legend = legend
|
|
62
60
|
self.figsize = figsize
|
|
63
61
|
self.plot_size = plot_size
|
|
@@ -109,23 +107,25 @@ class StackedAreaPlot:
|
|
|
109
107
|
|
|
110
108
|
style_spines(
|
|
111
109
|
ax,
|
|
112
|
-
decimals=self.
|
|
113
|
-
units=self.
|
|
110
|
+
decimals=self.fmt.decimals,
|
|
111
|
+
units=self.fmt.units,
|
|
114
112
|
**AX_CONFIG["spines"],
|
|
115
113
|
)
|
|
116
114
|
if self.baseline:
|
|
117
115
|
style_baseline(ax, 0, **AX_CONFIG["baseline"])
|
|
118
116
|
|
|
117
|
+
fig_width_px: float = fig.get_size_inches()[0] * fig.dpi
|
|
119
118
|
legend_ncol = self.legend.ncol if self.legend else None
|
|
120
|
-
ncol =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
else _DEFAULT_SEP
|
|
119
|
+
ncol = (
|
|
120
|
+
legend_ncol
|
|
121
|
+
if legend_ncol is not None
|
|
122
|
+
else auto_ncol(ax, labels, available_width_px=fig_width_px)
|
|
125
123
|
)
|
|
126
|
-
ax.
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
125
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
126
|
+
handles,
|
|
127
|
+
label_strs,
|
|
128
|
+
loc="outside lower center",
|
|
129
129
|
ncol=ncol,
|
|
130
130
|
)
|
|
131
131
|
|
|
@@ -163,7 +163,7 @@ class StackedBarPlot:
|
|
|
163
163
|
start_date: str | None = None,
|
|
164
164
|
end_date: str | None = None,
|
|
165
165
|
baseline: bool = True,
|
|
166
|
-
|
|
166
|
+
fmt: Format | None = None,
|
|
167
167
|
legend: Legend | None = None,
|
|
168
168
|
figsize: tuple[float, float] | None = None,
|
|
169
169
|
overlay_series: dict[str, str] | None = None,
|
|
@@ -180,7 +180,7 @@ class StackedBarPlot:
|
|
|
180
180
|
self.start_date = start_date
|
|
181
181
|
self.end_date = end_date
|
|
182
182
|
self.baseline = baseline
|
|
183
|
-
self.
|
|
183
|
+
self.fmt = fmt or Format()
|
|
184
184
|
self.legend = legend
|
|
185
185
|
self.plot_size = plot_size
|
|
186
186
|
self.figsize = figsize
|
|
@@ -313,8 +313,8 @@ class StackedBarPlot:
|
|
|
313
313
|
|
|
314
314
|
style_spines(
|
|
315
315
|
ax,
|
|
316
|
-
decimals=self.
|
|
317
|
-
units=self.
|
|
316
|
+
decimals=self.fmt.decimals,
|
|
317
|
+
units=self.fmt.units,
|
|
318
318
|
**AX_CONFIG["spines"],
|
|
319
319
|
)
|
|
320
320
|
if self.baseline:
|
|
@@ -323,20 +323,18 @@ class StackedBarPlot:
|
|
|
323
323
|
all_labels = list(self.series.values()) + list(
|
|
324
324
|
self.overlay_series.values()
|
|
325
325
|
)
|
|
326
|
+
fig_width_px: float = fig.get_size_inches()[0] * fig.dpi
|
|
326
327
|
legend_ncol = self.legend.ncol if self.legend else None
|
|
327
328
|
ncol = (
|
|
328
329
|
legend_ncol
|
|
329
330
|
if legend_ncol is not None
|
|
330
|
-
else auto_ncol(ax, all_labels)
|
|
331
|
-
)
|
|
332
|
-
sep = (
|
|
333
|
-
self.legend.sep
|
|
334
|
-
if self.legend and self.legend.sep is not None
|
|
335
|
-
else _DEFAULT_SEP
|
|
331
|
+
else auto_ncol(ax, all_labels, available_width_px=fig_width_px)
|
|
336
332
|
)
|
|
337
|
-
ax.
|
|
338
|
-
|
|
339
|
-
|
|
333
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
334
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
335
|
+
handles,
|
|
336
|
+
label_strs,
|
|
337
|
+
loc="outside lower center",
|
|
340
338
|
ncol=ncol,
|
|
341
339
|
)
|
|
342
340
|
|