tesorotools-python 0.0.42__tar.gz → 0.0.44__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.
Files changed (71) hide show
  1. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/.gitignore +26 -29
  2. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/PKG-INFO +1 -1
  3. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/pyproject.toml +69 -66
  4. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/__init__.py +3 -0
  5. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/artists/__init__.py +2 -1
  6. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/artists/barh_plot.py +207 -1
  7. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/README.md +4 -4
  8. tesorotools_python-0.0.44/src/tesorotools/assets/fonts/README.md +4 -0
  9. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/plots.yaml +44 -44
  10. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/tesoro.mplstyle +21 -21
  11. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/database/__init__.py +14 -14
  12. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/providers/ecb.py +217 -217
  13. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/content.py +17 -17
  14. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/images.py +159 -159
  15. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/table.py +505 -505
  16. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/testing/compare.py +147 -147
  17. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/utils/__init__.py +3 -3
  18. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/utils/format.py +59 -59
  19. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/utils/globals.py +18 -18
  20. tesorotools_python-0.0.44/src/tesorotools/utils/shortcuts.py +42 -0
  21. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/utils/template.py +132 -132
  22. tesorotools_python-0.0.42/src/tesorotools/assets/fonts/README.md +0 -1
  23. tesorotools_python-0.0.42/src/tesorotools/utils/shortcuts.py +0 -35
  24. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/_build_context.py +0 -0
  25. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/_registry.py +0 -0
  26. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/artists/_common.py +0 -0
  27. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/artists/line_plot.py +0 -0
  28. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/artists/stacked.py +0 -0
  29. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/artists/type_curve.py +0 -0
  30. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
  31. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
  32. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
  33. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
  34. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
  35. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
  36. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
  37. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
  38. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/data_sources/__init__.py +0 -0
  39. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/data_sources/debug.py +0 -0
  40. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/database/local.py +0 -0
  41. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/database/push.py +0 -0
  42. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/database/shared.py +0 -0
  43. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/dependencies/__init__.py +0 -0
  44. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/dependencies/node.py +0 -0
  45. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/dependencies/resolution.py +0 -0
  46. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/driver.py +0 -0
  47. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/manifest.py +0 -0
  48. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/offsets/__init__.py +0 -0
  49. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/offsets/offsets.py +0 -0
  50. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/offsets/outliers.py +0 -0
  51. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/orchestration.py +0 -0
  52. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/pipeline/__init__.py +0 -0
  53. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/pipeline/diagnose.py +0 -0
  54. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/pipeline/engine.py +0 -0
  55. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/pipeline/rules.py +0 -0
  56. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/providers/__init__.py +0 -0
  57. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/providers/base.py +0 -0
  58. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/providers/bde.py +0 -0
  59. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/providers/lseg.py +0 -0
  60. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/py.typed +0 -0
  61. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/__init__.py +0 -0
  62. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/__init__.py +0 -0
  63. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/section.py +0 -0
  64. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/subtitle.py +0 -0
  65. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/text.py +0 -0
  66. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/content/title.py +0 -0
  67. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/render/report.py +0 -0
  68. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/testing/__init__.py +0 -0
  69. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/utils/config.py +0 -0
  70. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/utils/matplotlib.py +0 -0
  71. {tesorotools_python-0.0.42 → tesorotools_python-0.0.44}/src/tesorotools/utils/series.py +0 -0
@@ -1,30 +1,27 @@
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
-
21
- test/
22
-
23
- # font files
24
- *.otf
25
-
26
- # coverage
27
- .coverage
28
-
29
- # ruff
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
+
21
+ test/
22
+
23
+ # coverage
24
+ .coverage
25
+
26
+ # ruff
30
27
  .ruff_cache/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tesorotools-python
3
- Version: 0.0.42
3
+ Version: 0.0.44
4
4
  Requires-Python: >=3.13
5
5
  Requires-Dist: babel>=2.17
6
6
  Requires-Dist: matplotlib>=3.10
@@ -1,66 +1,69 @@
1
- [project]
2
- name = "tesorotools-python"
3
- requires-python = ">=3.13"
4
- version = "0.0.42"
5
- dependencies = [
6
- # database and ORM
7
- "psycopg[binary]>=3.1",
8
- "SQLAlchemy>=2.0",
9
-
10
- # data analysis
11
- "pandas>=2.2",
12
- "pyarrow>=18.0",
13
- "openpyxl>=3.1",
14
-
15
- # utils
16
- "PyYAML>=6.0",
17
- "babel>=2.17",
18
-
19
- # data visualization
20
- "matplotlib>=3.10",
21
- "python-docx>=1.1",
22
-
23
- # os dependencies
24
- "pywin32>=311; sys_platform == 'win32'",
25
- ]
26
-
27
- [project.optional-dependencies]
28
- bde = ["requests>=2.31"]
29
- ecb = ["requests>=2.31"]
30
- lseg = ["lseg-data>=2.1"]
31
-
32
- [dependency-groups]
33
- dev = [
34
- "ruff>=0.8",
35
- "pyright>=1.1",
36
- "pre-commit>=4.0",
37
- "pandas-stubs>=2.2",
38
- "coverage>=7.0",
39
- ]
40
-
41
- [build-system]
42
- requires = ["hatchling"]
43
- build-backend = "hatchling.build"
44
-
45
- [tool.hatch.build.targets.wheel]
46
- packages = ["src/tesorotools"]
47
- artifacts = ["src/tesorotools/assets/fonts/*.otf"]
48
-
49
- [tool.hatch.build.targets.sdist]
50
- include = ["src/tesorotools"]
51
- artifacts = ["src/tesorotools/assets/fonts/*.otf"]
52
-
53
- [tool.coverage.report]
54
- exclude_lines = [
55
- 'if __name__ == "__main__"',
56
- '^\s*\.\.\.$',
57
- 'if TYPE_CHECKING',
58
- ]
59
-
60
- [tool.ruff]
61
- line-length = 80
62
-
63
- [tool.pyright]
64
- pythonVersion = "3.13"
65
- extraPaths = ["src"]
66
- typeCheckingMode = "strict"
1
+ [project]
2
+ name = "tesorotools-python"
3
+ requires-python = ">=3.13"
4
+ dynamic = ["version"]
5
+ dependencies = [
6
+ # database and ORM
7
+ "psycopg[binary]>=3.1",
8
+ "SQLAlchemy>=2.0",
9
+
10
+ # data analysis
11
+ "pandas>=2.2",
12
+ "pyarrow>=18.0",
13
+ "openpyxl>=3.1",
14
+
15
+ # utils
16
+ "PyYAML>=6.0",
17
+ "babel>=2.17",
18
+
19
+ # data visualization
20
+ "matplotlib>=3.10",
21
+ "python-docx>=1.1",
22
+
23
+ # os dependencies
24
+ "pywin32>=311; sys_platform == 'win32'",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ bde = ["requests>=2.31"]
29
+ ecb = ["requests>=2.31"]
30
+ lseg = ["lseg-data>=2.1"]
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "ruff>=0.8",
35
+ "pyright>=1.1",
36
+ "pre-commit>=4.0",
37
+ "pandas-stubs>=2.2",
38
+ "coverage>=7.0",
39
+ ]
40
+
41
+ [build-system]
42
+ requires = ["hatchling", "hatch-vcs"]
43
+ build-backend = "hatchling.build"
44
+
45
+ [tool.hatch.version]
46
+ source = "vcs"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/tesorotools"]
50
+ artifacts = ["src/tesorotools/assets/fonts/*.otf"]
51
+
52
+ [tool.hatch.build.targets.sdist]
53
+ include = ["src/tesorotools"]
54
+ artifacts = ["src/tesorotools/assets/fonts/*.otf"]
55
+
56
+ [tool.coverage.report]
57
+ exclude_lines = [
58
+ 'if __name__ == "__main__"',
59
+ '^\s*\.\.\.$',
60
+ 'if TYPE_CHECKING',
61
+ ]
62
+
63
+ [tool.ruff]
64
+ line-length = 80
65
+
66
+ [tool.pyright]
67
+ pythonVersion = "3.13"
68
+ extraPaths = ["src"]
69
+ typeCheckingMode = "strict"
@@ -45,6 +45,7 @@ from tesorotools._registry import (
45
45
  )
46
46
  from tesorotools.artists import (
47
47
  Format,
48
+ GroupedBarChart,
48
49
  HorizontalBarChart,
49
50
  Legend,
50
51
  LinePlot,
@@ -76,6 +77,7 @@ def _register_builtins() -> None:
76
77
  register_artist("stacked_area", StackedAreaPlot)
77
78
  register_artist("stacked_bar", StackedBarPlot)
78
79
  register_artist("barh", HorizontalBarChart)
80
+ register_artist("grouped_barh", GroupedBarChart)
79
81
  register_artist("type_curve", TypeCurve)
80
82
 
81
83
  register_tag("format", Format)
@@ -102,6 +104,7 @@ __all__ = [
102
104
  "DataProvider",
103
105
  "EcbProvider",
104
106
  "Format",
107
+ "GroupedBarChart",
105
108
  "HorizontalBarChart",
106
109
  "Image",
107
110
  "Images",
@@ -27,7 +27,7 @@ strictly image output.
27
27
  import matplotlib.style
28
28
 
29
29
  from tesorotools.artists._common import Artist, Format, Legend
30
- from tesorotools.artists.barh_plot import HorizontalBarChart
30
+ from tesorotools.artists.barh_plot import GroupedBarChart, HorizontalBarChart
31
31
  from tesorotools.artists.line_plot import LinePlot
32
32
  from tesorotools.artists.stacked import StackedAreaPlot, StackedBarPlot
33
33
  from tesorotools.artists.type_curve import TypeCurve
@@ -38,6 +38,7 @@ matplotlib.style.use(STYLE_SHEET)
38
38
  __all__ = [
39
39
  "Artist",
40
40
  "Format",
41
+ "GroupedBarChart",
41
42
  "HorizontalBarChart",
42
43
  "Legend",
43
44
  "LinePlot",
@@ -21,6 +21,7 @@ from pathlib import Path
21
21
  from typing import Any, Self
22
22
 
23
23
  import matplotlib.pyplot as plt
24
+ import numpy as np
24
25
  import pandas as pd
25
26
  from matplotlib.axes import Axes
26
27
  from matplotlib.container import BarContainer
@@ -32,6 +33,9 @@ from tesorotools.artists._common import (
32
33
  AX_CONFIG,
33
34
  FIG_CONFIG,
34
35
  Format,
36
+ Legend,
37
+ adjust_figure_for_plot_size,
38
+ auto_ncol,
35
39
  resolve_data,
36
40
  )
37
41
  from tesorotools.utils.matplotlib import PLOT_CONFIG, format_annotation
@@ -39,7 +43,7 @@ from tesorotools.utils.template import TemplateLoader
39
43
 
40
44
  BARH_CONFIG: dict[str, Any] = PLOT_CONFIG["barh"]
41
45
 
42
- __all__ = ["HorizontalBarChart"]
46
+ __all__ = ["GroupedBarChart", "HorizontalBarChart"]
43
47
 
44
48
 
45
49
  class _Col(Enum):
@@ -370,3 +374,205 @@ class HorizontalBarChart:
370
374
  )
371
375
  plt.close(fig)
372
376
  return ax
377
+
378
+
379
+ class GroupedBarChart:
380
+ """Grouped (clustered) horizontal bar chart artist.
381
+
382
+ Compares two or more series side-by-side within each
383
+ category -- the "double horizontal bar" the single-series
384
+ :class:`HorizontalBarChart` cannot express (e.g. rural vs.
385
+ urban, habitual vs. preferred). Categories run down the
386
+ y-axis; each category holds one bar per series, the i-th
387
+ series coloured with theme colour ``C{i}``.
388
+
389
+ The input ``data`` is a flat ``pd.DataFrame`` indexed by
390
+ category id with one column per series id; ``categories``
391
+ selects and labels the rows and ``series`` selects and
392
+ labels the columns, so the same wide frame can feed
393
+ several charts.
394
+
395
+ Parameters
396
+ ----------
397
+ out_path
398
+ ``.png`` path the rendered chart is saved to.
399
+ data, data_path
400
+ Provide one. The DataFrame is indexed by the ids in
401
+ ``categories`` and carries one column per id in
402
+ ``series``.
403
+ categories
404
+ ``id -> label`` for the y-axis rows, in display order
405
+ (unless ``sorted``).
406
+ series
407
+ ``id -> label`` for the bars within each category, in
408
+ colour order (first gets ``C0``).
409
+ fmt, annot_fmt
410
+ Number formats for the x-axis ticks and the per-bar
411
+ value annotations. ``annot_fmt`` defaults to ``fmt``.
412
+ annotate
413
+ Label each bar with its formatted value (default
414
+ ``True``).
415
+ sorted
416
+ Sort categories ascending by the first series' value
417
+ before drawing (default ``False`` -- preserve the
418
+ declared order, which usually mirrors a questionnaire).
419
+ group_width
420
+ Fraction of the category slot occupied by its bar
421
+ group (default ``0.8``); the rest is inter-group gap.
422
+ legend
423
+ Legend layout (``ncol``); when omitted the column
424
+ count is auto-fitted.
425
+ figsize, plot_size
426
+ Override ``FIG_CONFIG['figsize']`` / resize the figure
427
+ so the axes area matches ``plot_size`` after the
428
+ legend is placed.
429
+ """
430
+
431
+ def __init__(
432
+ self,
433
+ out_path: Path,
434
+ *,
435
+ data: pd.DataFrame | None = None,
436
+ data_path: Path | None = None,
437
+ categories: dict[str, str],
438
+ series: dict[str, str],
439
+ fmt: Format | None = None,
440
+ annot_fmt: Format | None = None,
441
+ annotate: bool = True,
442
+ sorted: bool = False,
443
+ group_width: float = 0.8,
444
+ legend: Legend | None = None,
445
+ figsize: tuple[float, float] | None = None,
446
+ plot_size: tuple[float, float] | None = None,
447
+ ) -> None:
448
+ if out_path.suffix != ".png":
449
+ raise ValueError(f"out_path must be .png: {out_path}")
450
+ if not series:
451
+ raise ValueError("Must declare at least one series")
452
+ self.out_path = out_path
453
+ self.data = resolve_data(data, data_path)
454
+ self.categories = categories
455
+ self.series = series
456
+ self.fmt = fmt or Format()
457
+ self.annot_fmt = annot_fmt or self.fmt
458
+ self.annotate = annotate
459
+ self.sorted = sorted
460
+ self.group_width = group_width
461
+ self.legend = legend
462
+ self.figsize = figsize
463
+ self.plot_size = plot_size
464
+
465
+ @classmethod
466
+ def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
467
+ """Build a :class:`GroupedBarChart` from ``!grouped_barh``.
468
+
469
+ Required keys
470
+ -------------
471
+ ``out_path``
472
+ ``.png`` destination path.
473
+ ``data_path``
474
+ ``.feather`` file with a DataFrame indexed by
475
+ category id and one column per series id.
476
+ ``categories``
477
+ ``id -> label`` for the y-axis rows.
478
+ ``series``
479
+ ``id -> label`` for the bars within each category.
480
+
481
+ Optional keys
482
+ -------------
483
+ ``fmt`` (``!format``), ``annot_fmt`` (``!format``),
484
+ ``annotate`` (default ``True``), ``sorted`` (default
485
+ ``False``), ``group_width`` (default ``0.8``),
486
+ ``legend`` (``!legend``), ``figsize``, ``plot_size``.
487
+
488
+ Example
489
+ -------
490
+ .. code-block:: yaml
491
+
492
+ mayores_rural_urbano: !grouped_barh
493
+ out_path: out/b7_mayores.png
494
+ data_path: data/b7.feather
495
+ categories:
496
+ banca_online: "Usa banca online"
497
+ cajero: "Usa el cajero"
498
+ oficina: "Acude a la oficina"
499
+ series:
500
+ rural: "Rural (< 5.000 hab.)"
501
+ urban: "Mayores (urbano)"
502
+ fmt: !format {decimals: 1, units: "%"}
503
+ """
504
+ cfg: dict[str, Any] = loader.construct_mapping( # type: ignore[assignment]
505
+ node, deep=True
506
+ )
507
+ cfg.pop("id")
508
+ cfg["out_path"] = Path(cfg["out_path"])
509
+ if "data_path" in cfg:
510
+ cfg["data_path"] = Path(cfg["data_path"])
511
+ return cls(**cfg)
512
+
513
+ def plot(self) -> Axes:
514
+ """Render the chart and persist it to ``self.out_path``."""
515
+ cat_ids = list(self.categories.keys())
516
+ ser_ids = list(self.series.keys())
517
+ frame = self.data.loc[cat_ids, ser_ids].copy()
518
+ if self.sorted:
519
+ frame = frame.sort_values(by=ser_ids[0])
520
+ cat_ids = list(frame.index)
521
+ cat_labels = [self.categories[c] for c in cat_ids]
522
+ ser_labels = list(self.series.values())
523
+
524
+ fig_kw = dict(FIG_CONFIG)
525
+ if self.figsize is not None:
526
+ fig_kw["figsize"] = self.figsize
527
+ fig: Figure = plt.figure( # type: ignore[reportUnknownMemberType]
528
+ **fig_kw
529
+ )
530
+ ax: Axes = fig.add_subplot()
531
+
532
+ n = len(ser_ids)
533
+ y = np.arange(len(cat_ids))
534
+ height = self.group_width / n
535
+ for k, sid in enumerate(ser_ids):
536
+ # First series sits at the top of each category slot;
537
+ # offsets march downward so colour order reads top-to-bottom.
538
+ offset = (n - 1) / 2 * height - k * height
539
+ container: BarContainer = ax.barh( # type: ignore[reportUnknownMemberType]
540
+ y=y + offset,
541
+ width=frame[sid].to_numpy(dtype=np.float64),
542
+ height=height,
543
+ color=f"C{k}",
544
+ label=ser_labels[k],
545
+ )
546
+ if self.annotate:
547
+ _annotate_bars(fig, ax, container, self.annot_fmt)
548
+
549
+ ax.set_yticks(y) # type: ignore[reportUnknownMemberType]
550
+ ax.set_yticklabels( # type: ignore[reportUnknownMemberType]
551
+ cat_labels
552
+ )
553
+ _style_axes(ax, self.fmt, **AX_CONFIG["spines"])
554
+ _style_zero_baseline(ax, **AX_CONFIG["baseline"])
555
+
556
+ fig_width_px: float = fig.get_size_inches()[0] * fig.dpi
557
+ legend_ncol = self.legend.ncol if self.legend else None
558
+ ncol = (
559
+ legend_ncol
560
+ if legend_ncol is not None
561
+ else auto_ncol(ax, ser_labels, available_width_px=fig_width_px)
562
+ )
563
+ handles, label_strs = ax.get_legend_handles_labels()
564
+ fig.legend( # type: ignore[reportUnknownMemberType]
565
+ handles,
566
+ label_strs,
567
+ loc="outside lower center",
568
+ ncol=ncol,
569
+ )
570
+
571
+ if self.plot_size is not None:
572
+ adjust_figure_for_plot_size(fig, ax, self.plot_size)
573
+
574
+ fig.savefig( # type: ignore[reportUnknownMemberType]
575
+ self.out_path
576
+ )
577
+ plt.close(fig)
578
+ return ax
@@ -1,5 +1,5 @@
1
- En esta carpeta se guardarán todos los *assets* del proyecto, como
2
-
3
- - [fonts](fonts/): Fuentes (archivos *.otf).
4
- - [plots.yaml](plots.yaml): Archivo de configuración general para los gráficos.
1
+ En esta carpeta se guardarán todos los *assets* del proyecto, como
2
+
3
+ - [fonts](fonts/): Fuentes (archivos *.otf).
4
+ - [plots.yaml](plots.yaml): Archivo de configuración general para los gráficos.
5
5
  - [tesoro.mplstyle](tesoro.mplstyle): Archivo de estilos *matplotlib* para los gráficos.
@@ -0,0 +1,4 @@
1
+ Archivos *.otf de fuentes (Cabinet Grotesk). Se versionan con **Git LFS**
2
+ (ver `.gitattributes`), no como blobs en la historia normal de git. El
3
+ checkout los materializa, y `pyproject.toml` los empaqueta en el wheel
4
+ publicado vía `tool.hatch.build ... artifacts`.
@@ -1,44 +1,44 @@
1
- # general plot config file
2
-
3
- style:
4
- font: Cabinet Grotesk
5
-
6
- figure:
7
- dpi: 500
8
- layout: constrained
9
-
10
- ax:
11
- spines:
12
- color: gray
13
- linewidth: 1
14
- baseline:
15
- color: "#d9e1fc"
16
- linestyle: solid
17
- linewidth: 1
18
- zorder: 1
19
-
20
- type_curve:
21
- last:
22
- alpha: 0.2
23
- color: C1
24
- current:
25
- alpha: 0.3
26
- color: C0
27
- line:
28
- linewidth: 2
29
- marker: D
30
- color: C0
31
-
32
- barh:
33
- highlight_factor: 0.4
34
- padding: 5
35
-
36
- line:
37
- ncol: 5
38
-
39
- table:
40
- style: Light Shading Accent 1
41
- autofit: False
42
- block_separator:
43
- fill: "BFBFBF" # hex color of the inter-block band
44
- height_twips: 20 # half-band height; total band = 2 * height_twips
1
+ # general plot config file
2
+
3
+ style:
4
+ font: Cabinet Grotesk
5
+
6
+ figure:
7
+ dpi: 500
8
+ layout: constrained
9
+
10
+ ax:
11
+ spines:
12
+ color: gray
13
+ linewidth: 1
14
+ baseline:
15
+ color: "#d9e1fc"
16
+ linestyle: solid
17
+ linewidth: 1
18
+ zorder: 1
19
+
20
+ type_curve:
21
+ last:
22
+ alpha: 0.2
23
+ color: C1
24
+ current:
25
+ alpha: 0.3
26
+ color: C0
27
+ line:
28
+ linewidth: 2
29
+ marker: D
30
+ color: C0
31
+
32
+ barh:
33
+ highlight_factor: 0.4
34
+ padding: 5
35
+
36
+ line:
37
+ ncol: 5
38
+
39
+ table:
40
+ style: Light Shading Accent 1
41
+ autofit: False
42
+ block_separator:
43
+ fill: "BFBFBF" # hex color of the inter-block band
44
+ height_twips: 20 # half-band height; total band = 2 * height_twips
@@ -1,21 +1,21 @@
1
- # fonts
2
- font.size: 12.0
3
- xtick.labelsize: 12.0
4
- ytick.labelsize: 12.0
5
- legend.fontsize: 12.0
6
-
7
- # modified dashed_pattern
8
- lines.dashed_pattern: 10, 5
9
-
10
- # markers
11
- lines.markersize: 8
12
-
13
- # grids
14
- grid.color: d9e1fc
15
- grid.linestyle: dashed
16
- grid.linewidth: 0.5
17
- grid.alpha: 0.5
18
-
19
- # color palette
20
- axes.prop_cycle: cycler('color', ['001e93', 'ffbd4c', '6d6d6d', '000000', 'e6821e', '2957ff', 'fba737', '5da6ff', '00ebb2', '6d0000', '009200', 'df2871', 'd9e1fc', 'ffddab'])
21
-
1
+ # fonts
2
+ font.size: 12.0
3
+ xtick.labelsize: 12.0
4
+ ytick.labelsize: 12.0
5
+ legend.fontsize: 12.0
6
+
7
+ # modified dashed_pattern
8
+ lines.dashed_pattern: 10, 5
9
+
10
+ # markers
11
+ lines.markersize: 8
12
+
13
+ # grids
14
+ grid.color: d9e1fc
15
+ grid.linestyle: dashed
16
+ grid.linewidth: 0.5
17
+ grid.alpha: 0.5
18
+
19
+ # color palette
20
+ axes.prop_cycle: cycler('color', ['001e93', 'ffbd4c', '6d6d6d', '000000', 'e6821e', '2957ff', 'fba737', '5da6ff', '00ebb2', '6d0000', '009200', 'df2871', 'd9e1fc', 'ffddab'])
21
+
@@ -1,14 +1,14 @@
1
- """
2
- Módulo de gestión de bases de datos y persistencia para tesorotools.
3
- Contiene las utilidades para interactuar con bases de datos locales y remotas.
4
- """
5
-
6
- from tesorotools.database.local import LocalDatabase, ShortcutDatabase
7
- from tesorotools.database.shared import SharedDatabase, resolve_shared_root
8
-
9
- __all__ = [
10
- "LocalDatabase",
11
- "SharedDatabase",
12
- "ShortcutDatabase",
13
- "resolve_shared_root",
14
- ]
1
+ """
2
+ Módulo de gestión de bases de datos y persistencia para tesorotools.
3
+ Contiene las utilidades para interactuar con bases de datos locales y remotas.
4
+ """
5
+
6
+ from tesorotools.database.local import LocalDatabase, ShortcutDatabase
7
+ from tesorotools.database.shared import SharedDatabase, resolve_shared_root
8
+
9
+ __all__ = [
10
+ "LocalDatabase",
11
+ "SharedDatabase",
12
+ "ShortcutDatabase",
13
+ "resolve_shared_root",
14
+ ]