tesorotools-python 0.1.3__tar.gz → 0.1.4__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 (149) hide show
  1. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/PKG-INFO +1 -1
  2. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/pyproject.toml +1 -0
  3. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/__init__.py +6 -0
  4. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/__init__.py +4 -0
  5. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/_common.py +55 -1
  6. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/bar_line.py +2 -1
  7. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/box_plot.py +2 -1
  8. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/intraday_plot.py +2 -1
  9. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/line_plot.py +4 -1
  10. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/plotly_backend.py +142 -1
  11. tesorotools_python-0.1.4/src/tesorotools/artists/sankey.py +436 -0
  12. tesorotools_python-0.1.4/src/tesorotools/artists/scatter.py +469 -0
  13. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/shock_plot.py +2 -1
  14. tesorotools_python-0.1.4/src/tesorotools/artists/tikz_backend.py +172 -0
  15. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/waterfall.py +2 -1
  16. tesorotools_python-0.1.4/src/tesorotools/assets/nota_template.dotx +0 -0
  17. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/plots.yaml +40 -0
  18. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/style/tesoro.sty +3 -0
  19. tesorotools_python-0.1.4/src/tesorotools/note/__init__.py +40 -0
  20. tesorotools_python-0.1.4/src/tesorotools/note/builder.py +749 -0
  21. tesorotools_python-0.1.4/src/tesorotools/note/cli.py +168 -0
  22. tesorotools_python-0.1.4/src/tesorotools/note/inline.py +129 -0
  23. tesorotools_python-0.1.4/src/tesorotools/note/lint.py +291 -0
  24. tesorotools_python-0.1.4/src/tesorotools/note/markdown.py +613 -0
  25. tesorotools_python-0.1.4/src/tesorotools/note/skeleton.md +75 -0
  26. tesorotools_python-0.1.4/src/tesorotools/note/skill/SKILL.md +171 -0
  27. tesorotools_python-0.1.4/src/tesorotools/note/skill/reglas.md +120 -0
  28. tesorotools_python-0.1.4/src/tesorotools/note/template.py +120 -0
  29. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/globals.py +4 -0
  30. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/app.py +11 -2
  31. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/brand.py +10 -1
  32. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/components.py +32 -12
  33. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/theme.py +29 -6
  34. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/.gitignore +0 -0
  35. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/_build_context.py +0 -0
  36. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/_registry.py +0 -0
  37. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/barh_plot.py +0 -0
  38. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/compact.py +0 -0
  39. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/matrix.py +0 -0
  40. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/sharing.py +0 -0
  41. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/stacked.py +0 -0
  42. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/type_curve.py +0 -0
  43. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/artists/vector_plot.py +0 -0
  44. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/README.md +0 -0
  45. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
  46. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
  47. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
  48. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
  49. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
  50. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
  51. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
  52. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
  53. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/fonts/README.md +0 -0
  54. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/template.docx +0 -0
  55. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/assets/tesoro.mplstyle +0 -0
  56. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/data_sources/__init__.py +0 -0
  57. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/data_sources/debug.py +0 -0
  58. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/database/__init__.py +0 -0
  59. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/database/local.py +0 -0
  60. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/database/push.py +0 -0
  61. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/database/shared.py +0 -0
  62. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/dependencies/__init__.py +0 -0
  63. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/dependencies/node.py +0 -0
  64. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/dependencies/resolution.py +0 -0
  65. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/driver.py +0 -0
  66. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/__init__.py +0 -0
  67. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/cli.py +0 -0
  68. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/scaffold.py +0 -0
  69. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/main.tex.tmpl +0 -0
  70. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/sections/01-introduccion.tex +0 -0
  71. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/sections/02-analisis.tex +0 -0
  72. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/article/sections/03-conclusiones.tex +0 -0
  73. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/main.tex.tmpl +0 -0
  74. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/slides/01-contexto.tex +0 -0
  75. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/slides/02-analisis.tex +0 -0
  76. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/slides/03-conclusiones.tex +0 -0
  77. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/beamer/style/beamerthemeTesoro.sty +0 -0
  78. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/appendices/a-fuentes.tex +0 -0
  79. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/chapters/01-introduccion.tex +0 -0
  80. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/chapters/02-analisis.tex +0 -0
  81. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/frontmatter/resumen.tex +0 -0
  82. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/book/main.tex.tmpl +0 -0
  83. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/README.md.tmpl +0 -0
  84. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/dot-gitignore +0 -0
  85. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/dot-vscode/settings.json +0 -0
  86. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/figures/dot-gitkeep +0 -0
  87. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/fonts/README.md +0 -0
  88. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/latexmkrc +0 -0
  89. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/refs.bib +0 -0
  90. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/style/tesorobrand.sty.tmpl +0 -0
  91. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/latex/templates/common/style/tesorolocal.sty.tmpl +0 -0
  92. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/manifest.py +0 -0
  93. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/offsets/__init__.py +0 -0
  94. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/offsets/offsets.py +0 -0
  95. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/offsets/outliers.py +0 -0
  96. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/orchestration.py +0 -0
  97. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/palette.py +0 -0
  98. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/__init__.py +0 -0
  99. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/diagnose.py +0 -0
  100. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/engine.py +0 -0
  101. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/pipeline/rules.py +0 -0
  102. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/providers/__init__.py +0 -0
  103. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/providers/base.py +0 -0
  104. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/providers/bde.py +0 -0
  105. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/providers/ecb.py +0 -0
  106. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/providers/fred.py +0 -0
  107. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/providers/imf_irfcl.py +0 -0
  108. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/providers/lseg.py +0 -0
  109. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/py.typed +0 -0
  110. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/__init__.py +0 -0
  111. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/__init__.py +0 -0
  112. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/content.py +0 -0
  113. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/images.py +0 -0
  114. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/section.py +0 -0
  115. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/subtitle.py +0 -0
  116. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/table.py +0 -0
  117. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/text.py +0 -0
  118. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/content/title.py +0 -0
  119. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/document.py +0 -0
  120. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/render/report.py +0 -0
  121. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/scaffold.py +0 -0
  122. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/testing/__init__.py +0 -0
  123. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/testing/compare.py +0 -0
  124. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/__init__.py +0 -0
  125. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/config.py +0 -0
  126. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/format.py +0 -0
  127. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/matplotlib.py +0 -0
  128. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/series.py +0 -0
  129. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/shortcuts.py +0 -0
  130. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/utils/template.py +0 -0
  131. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/__init__.py +0 -0
  132. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/cli.py +0 -0
  133. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/formatting.py +0 -0
  134. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/launcher.py +0 -0
  135. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/palette.py +0 -0
  136. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/plotting.py +0 -0
  137. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/scaffold.py +0 -0
  138. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/Dockerfile.tmpl +0 -0
  139. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/README.md.tmpl +0 -0
  140. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/dot-dockerignore.tmpl +0 -0
  141. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/dot-gitignore.tmpl +0 -0
  142. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/dot-streamlit/config.toml.tmpl +0 -0
  143. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/pyproject.toml.tmpl +0 -0
  144. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/__init__.py.tmpl +0 -0
  145. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/cli.py.tmpl +0 -0
  146. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/main.py.tmpl +0 -0
  147. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/src/$package/sections.py.tmpl +0 -0
  148. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/tests/__init__.py.tmpl +0 -0
  149. {tesorotools_python-0.1.3 → tesorotools_python-0.1.4}/src/tesorotools/web/templates/tests/test_app.py.tmpl +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tesorotools-python
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Requires-Python: >=3.13
5
5
  Requires-Dist: babel>=2.17
6
6
  Requires-Dist: matplotlib>=3.10
@@ -38,6 +38,7 @@ web = ["streamlit>=1.62", "plotly>=5.24"]
38
38
  [project.scripts]
39
39
  tesorotools-web = "tesorotools.web.cli:main"
40
40
  tesorotools-latex = "tesorotools.latex.cli:main"
41
+ tesorotools-nota = "tesorotools.note.cli:main"
41
42
 
42
43
  [dependency-groups]
43
44
  dev = [
@@ -66,6 +66,8 @@ from tesorotools.artists import (
66
66
  Legend,
67
67
  LinePlot,
68
68
  MatrixChart,
69
+ SankeyPlot,
70
+ ScatterPlot,
69
71
  ShockChart,
70
72
  StackedAreaPlot,
71
73
  StackedBarPlot,
@@ -108,6 +110,8 @@ def _register_builtins() -> None:
108
110
  register_artist("shock_plot", ShockChart)
109
111
  register_artist("intraday", IntradayPlot)
110
112
  register_artist("waterfall", Waterfall)
113
+ register_artist("sankey", SankeyPlot)
114
+ register_artist("scatter", ScatterPlot)
111
115
 
112
116
  register_tag("format", Format)
113
117
  register_tag("legend", Legend)
@@ -149,6 +153,8 @@ __all__ = [
149
153
  "MatrixChart",
150
154
  "RegistryProtocol",
151
155
  "Report",
156
+ "SankeyPlot",
157
+ "ScatterPlot",
152
158
  "Section",
153
159
  "ShockChart",
154
160
  "StackedAreaPlot",
@@ -45,6 +45,8 @@ from tesorotools.artists.box_plot import BoxPlot
45
45
  from tesorotools.artists.intraday_plot import IntradayPlot
46
46
  from tesorotools.artists.line_plot import LinePlot
47
47
  from tesorotools.artists.matrix import MatrixChart
48
+ from tesorotools.artists.sankey import SankeyPlot
49
+ from tesorotools.artists.scatter import ScatterPlot
48
50
  from tesorotools.artists.sharing import share_y_axes
49
51
  from tesorotools.artists.shock_plot import ShockChart
50
52
  from tesorotools.artists.stacked import StackedAreaPlot, StackedBarPlot
@@ -64,6 +66,8 @@ __all__ = [
64
66
  "Legend",
65
67
  "LinePlot",
66
68
  "MatrixChart",
69
+ "SankeyPlot",
70
+ "ScatterPlot",
67
71
  "ShockChart",
68
72
  "StackedAreaPlot",
69
73
  "StackedBarPlot",
@@ -23,11 +23,13 @@ overrides only need to update ``plots.yaml``.
23
23
 
24
24
  from __future__ import annotations
25
25
 
26
+ import math
26
27
  from collections.abc import Sequence
27
28
  from pathlib import Path
28
29
  from typing import Any, Literal, Protocol, Self, cast, runtime_checkable
29
30
 
30
31
  import matplotlib.pyplot as plt
32
+ import numpy as np
31
33
  import pandas as pd
32
34
  from matplotlib.axes import Axes
33
35
  from matplotlib.dates import (
@@ -152,7 +154,7 @@ def plotly_unsupported(artist: object) -> frozenset[str]:
152
154
 
153
155
 
154
156
  class FigureLifecycle:
155
- """``save()`` and ``plot()`` on top of a subclass's ``build()``.
157
+ """``save()``, ``plot()`` and ``to_tikz()`` on top of ``build()``.
156
158
 
157
159
  Gives an artist the same three-step life cycle as the rest:
158
160
  ``build()`` returns the figure in memory, ``save()`` writes it
@@ -194,6 +196,17 @@ class FigureLifecycle:
194
196
  plt.close(fig)
195
197
  return ax
196
198
 
199
+ def to_tikz(self, path: Path | None = None) -> Path:
200
+ """Write the chart as PGF code for LaTeX and return the path.
201
+
202
+ By default next to the PNG, with a ``.pgf`` suffix; the document
203
+ includes it with ``\\input``. Needs a TeX installation (see
204
+ :mod:`tesorotools.artists.tikz_backend`).
205
+ """
206
+ from tesorotools.artists.tikz_backend import render_tikz
207
+
208
+ return render_tikz(self, path)
209
+
197
210
 
198
211
  # ----------------------------------------------------------------------
199
212
  # Structural contract for image artists
@@ -980,6 +993,40 @@ def apply_date_axis(
980
993
  label.set_rotation_mode("anchor")
981
994
 
982
995
 
996
+ class DecimalsLocator(MaxNLocator):
997
+ """Matplotlib's default y ticks, kept printable at *decimals*.
998
+
999
+ The default locator steps by 1, 2, 2.5 or 5 of a decade, so with a
1000
+ tick format of two decimals it can put ticks every 0.005, and two
1001
+ neighbours print alike (``1,17`` twice). When the step is not a
1002
+ whole multiple of ``10 ** -decimals`` (the unit) the next of 1, 2 or
1003
+ 5 units takes its place: fewer ticks, all different. Otherwise the
1004
+ ticks are exactly the default ones.
1005
+ """
1006
+
1007
+ def __init__(self, decimals: int, nbins: int | Literal["auto"] = "auto"):
1008
+ super().__init__( # type: ignore[reportUnknownMemberType]
1009
+ nbins=nbins, steps=[1, 2, 2.5, 5, 10]
1010
+ )
1011
+ self.decimals = decimals
1012
+
1013
+ def tick_values(self, vmin: float, vmax: float) -> Any:
1014
+ ticks = super().tick_values(vmin, vmax)
1015
+ if len(ticks) < 2:
1016
+ return ticks
1017
+ unit = 10.0**-self.decimals
1018
+ step = float(ticks[1] - ticks[0])
1019
+ ratio = step / unit
1020
+ if round(ratio) >= 1 and math.isclose(ratio, round(ratio)):
1021
+ return ticks
1022
+ # The default steps are 1, 2, 2.5 or 5 times a power of ten, so a
1023
+ # step that is no whole multiple of the unit is below 5 units.
1024
+ new = next(f * unit for f in (1, 2, 5) if f * unit >= step)
1025
+ low = math.floor(min(vmin, vmax) / new) * new
1026
+ high = math.ceil(max(vmin, vmax) / new) * new
1027
+ return np.arange(low, high + new / 2, new)
1028
+
1029
+
983
1030
  def compact_y_axis(ax: Axes) -> None:
984
1031
  """Denser y-ticks for compact charts (more reference numbers).
985
1032
 
@@ -994,6 +1041,12 @@ def compact_y_axis(ax: Axes) -> None:
994
1041
  raw: Any = COMPACT_CONFIG.get("y_max_ticks")
995
1042
  if raw is None:
996
1043
  return
1044
+ current = ax.yaxis.get_major_locator()
1045
+ if isinstance(current, DecimalsLocator):
1046
+ ax.yaxis.set_major_locator(
1047
+ DecimalsLocator(current.decimals, nbins=int(raw))
1048
+ )
1049
+ return
997
1050
  ax.yaxis.set_major_locator(
998
1051
  MaxNLocator(nbins=int(raw), steps=[1, 2, 2.5, 5, 10])
999
1052
  )
@@ -1288,6 +1341,7 @@ def style_spines(
1288
1341
  return format_annotation(y, decimals, units)
1289
1342
 
1290
1343
  ax.yaxis.set_major_formatter(FuncFormatter(_fmt_tick))
1344
+ ax.yaxis.set_major_locator(DecimalsLocator(decimals))
1291
1345
  ax.set_xlabel( # type: ignore[reportUnknownMemberType]
1292
1346
  ""
1293
1347
  )
@@ -42,6 +42,7 @@ from matplotlib.figure import Figure
42
42
  from yaml.nodes import MappingNode
43
43
 
44
44
  from tesorotools.artists._common import (
45
+ FigureLifecycle,
45
46
  AX_CONFIG,
46
47
  Format,
47
48
  Legend,
@@ -66,7 +67,7 @@ __all__ = ["BarLinePlot"]
66
67
  BAR_LINE_CONFIG: dict[str, Any] = PLOT_CONFIG["bar_line"]
67
68
 
68
69
 
69
- class BarLinePlot:
70
+ class BarLinePlot(FigureLifecycle):
70
71
  """Combo chart: vertical bars and marked lines, optionally on two axes.
71
72
 
72
73
  Parameters mirror the other time-series artists where they overlap
@@ -44,6 +44,7 @@ from matplotlib.patches import Patch
44
44
  from yaml.nodes import MappingNode
45
45
 
46
46
  from tesorotools.artists._common import (
47
+ FigureLifecycle,
47
48
  AX_CONFIG,
48
49
  Format,
49
50
  Legend,
@@ -105,7 +106,7 @@ def _weighted_quantile(
105
106
  return float(np.interp(q, cum, v))
106
107
 
107
108
 
108
- class BoxPlot:
109
+ class BoxPlot(FigureLifecycle):
109
110
  """Box-and-whisker chart, one box per category.
110
111
 
111
112
  Parameters mirror the other artists where they overlap (``out_path``,
@@ -42,6 +42,7 @@ from matplotlib.figure import Figure
42
42
  from yaml.nodes import MappingNode
43
43
 
44
44
  from tesorotools.artists._common import (
45
+ FigureLifecycle,
45
46
  AX_CONFIG,
46
47
  Format,
47
48
  Legend,
@@ -66,7 +67,7 @@ __all__ = ["IntradayPlot"]
66
67
  _MAX_XTICKS = 10
67
68
 
68
69
 
69
- class IntradayPlot:
70
+ class IntradayPlot(FigureLifecycle):
70
71
  """Single-instrument intraday line chart on a gap-free ordinal axis.
71
72
 
72
73
  Parameters mirror the other image artists where they overlap
@@ -23,6 +23,8 @@ from matplotlib.ticker import FuncFormatter
23
23
  from yaml.nodes import MappingNode
24
24
 
25
25
  from tesorotools.artists._common import (
26
+ DecimalsLocator,
27
+ FigureLifecycle,
26
28
  AX_CONFIG,
27
29
  DATE_DENSITY_LEVELS,
28
30
  Format,
@@ -56,7 +58,7 @@ locale.setlocale(locale.LC_ALL, "")
56
58
  __all__ = ["LinePlot"]
57
59
 
58
60
 
59
- class LinePlot:
61
+ class LinePlot(FigureLifecycle):
60
62
  """Time-series line chart with the tesorotools visual style.
61
63
 
62
64
  Parameters mirror the other image artists where they
@@ -542,6 +544,7 @@ class LinePlot:
542
544
  return format_annotation(y, axis_decimals, units)
543
545
 
544
546
  ax.yaxis.set_major_formatter(FuncFormatter(_fmt_axis_tick))
547
+ ax.yaxis.set_major_locator(DecimalsLocator(axis_decimals))
545
548
  if self.baseline:
546
549
  reference = 100 if self.base_100 else 0
547
550
  style_baseline(ax, reference, **AX_CONFIG["baseline"])
@@ -20,7 +20,7 @@ never depends on plotly.
20
20
 
21
21
  Coverage: every image artist except ``IntradayPlot`` -- line, type
22
22
  curve, stacked area, stacked bar, bar+line, box, shock, waterfall,
23
- horizontal bar, grouped bar, matrix and vector.
23
+ horizontal bar, grouped bar, matrix, vector, sankey and scatter.
24
24
 
25
25
  Known mimicry caveats (the styling that does NOT transfer for free between
26
26
  backends, per the dual-backend research):
@@ -64,6 +64,8 @@ if TYPE_CHECKING:
64
64
  )
65
65
  from tesorotools.artists.line_plot import LinePlot
66
66
  from tesorotools.artists.matrix import MatrixChart
67
+ from tesorotools.artists.sankey import SankeyPlot
68
+ from tesorotools.artists.scatter import ScatterPlot
67
69
  from tesorotools.artists.shock_plot import ShockChart
68
70
  from tesorotools.artists.stacked import StackedAreaPlot, StackedBarPlot
69
71
  from tesorotools.artists.type_curve import TypeCurve
@@ -78,6 +80,8 @@ __all__ = [
78
80
  "house_template",
79
81
  "line_plot_to_figure",
80
82
  "matrix_to_figure",
83
+ "sankey_to_figure",
84
+ "scatter_to_figure",
81
85
  "shock_chart_to_figure",
82
86
  "stacked_area_to_figure",
83
87
  "stacked_bar_to_figure",
@@ -1092,3 +1096,140 @@ def vector_plot_to_figure(chart: VectorPlot) -> go.Figure:
1092
1096
  ticksuffix=chart.y_fmt.units or "",
1093
1097
  )
1094
1098
  return fig
1099
+
1100
+
1101
+ def sankey_to_figure(chart: SankeyPlot) -> go.Figure:
1102
+ """Interactive ``Figure`` from a :class:`SankeyPlot` (``go.Sankey``).
1103
+
1104
+ Consumes the same layout as ``SankeyPlot.build``: one Plotly node per
1105
+ (stage, node) pair, pinned at its column and at the vertical centre
1106
+ the static chart gives it (``arrangement="fixed"``), and one link per
1107
+ flow coloured like its source node with ``sankey.flow_alpha``. Node
1108
+ labels carry the node value, as the end columns of the PNG do.
1109
+ """
1110
+ from tesorotools.artists.sankey import SANKEY_CONFIG
1111
+
1112
+ lay = chart._layout()
1113
+ n_col = len(lay.columns)
1114
+ alpha = float(SANKEY_CONFIG.get("flow_alpha", 0.45))
1115
+ index: dict[tuple[int, str], int] = {}
1116
+ labels: list[str] = []
1117
+ colors: list[str] = []
1118
+ xs: list[float] = []
1119
+ ys: list[float] = []
1120
+ for (c, n), top in lay.tops.items():
1121
+ index[(c, n)] = len(labels)
1122
+ height = lay.heights[(c, n)]
1123
+ labels.append(f"{chart.label(n)} {chart.value_text(height)}")
1124
+ colors.append(_color(chart.color(n)))
1125
+ # Plotly pins nodes strictly inside (0, 1); y grows downwards.
1126
+ xs.append(min(max(c / (n_col - 1), 0.001), 0.999))
1127
+ centre = 1 - (top - height / 2) / lay.total_height
1128
+ ys.append(min(max(centre, 0.001), 0.999))
1129
+ fig = _new_figure()
1130
+ fig.add_trace(
1131
+ go.Sankey(
1132
+ arrangement="fixed",
1133
+ valueformat=f".{chart.fmt.decimals}f",
1134
+ valuesuffix=chart.fmt.units or "",
1135
+ node={
1136
+ "label": labels,
1137
+ "color": colors,
1138
+ "x": xs,
1139
+ "y": ys,
1140
+ "pad": 10,
1141
+ "thickness": 12,
1142
+ "line": {"width": 0},
1143
+ },
1144
+ link={
1145
+ "source": [index[(f.column, f.source)] for f in lay.flows],
1146
+ "target": [index[(f.column + 1, f.target)] for f in lay.flows],
1147
+ "value": [f.value for f in lay.flows],
1148
+ "color": [
1149
+ _color(chart.color(f.source), alpha) for f in lay.flows
1150
+ ],
1151
+ },
1152
+ )
1153
+ )
1154
+ for c, text in enumerate(lay.stage_labels):
1155
+ fig.add_annotation(
1156
+ x=c / (n_col - 1),
1157
+ y=1.06,
1158
+ xref="paper",
1159
+ yref="paper",
1160
+ text=f"<b>{text}</b>",
1161
+ showarrow=False,
1162
+ )
1163
+ return fig
1164
+
1165
+
1166
+ #: ``ScatterPlot`` label side -> Plotly ``textposition``.
1167
+ _TEXT_POSITION = {
1168
+ "above right": "top right",
1169
+ "below right": "bottom right",
1170
+ "above left": "top left",
1171
+ "below left": "bottom left",
1172
+ "right": "middle right",
1173
+ "left": "middle left",
1174
+ "above": "top center",
1175
+ "below": "bottom center",
1176
+ }
1177
+
1178
+
1179
+ def scatter_to_figure(chart: ScatterPlot) -> go.Figure:
1180
+ """Interactive ``Figure`` from a :class:`ScatterPlot`.
1181
+
1182
+ One ``go.Scatter`` trace per group (so the legend lists the groups),
1183
+ each point labelled on the side the PNG chose
1184
+ (:meth:`ScatterPlot.placements`), with the same colours and limits.
1185
+ """
1186
+ from tesorotools.artists.scatter import SCATTER_CONFIG
1187
+
1188
+ rows = chart._rows()
1189
+ order = chart.group_order(rows)
1190
+ sides = chart.placements() if chart.labels else {}
1191
+ fig = _new_figure()
1192
+ for group in order:
1193
+ points = rows[rows["group"] == group]
1194
+ color = _color(chart.color(group, order))
1195
+ fig.add_trace(
1196
+ go.Scatter(
1197
+ x=points["x"],
1198
+ y=points["y"],
1199
+ mode="markers+text" if chart.labels else "markers",
1200
+ name=chart.group_label(group),
1201
+ showlegend=bool(chart.group) and chart.legend is not None,
1202
+ text=[chart.series[c] for c in points.index],
1203
+ textposition=[
1204
+ _TEXT_POSITION[sides.get(str(c), "above right")]
1205
+ for c in points.index
1206
+ ],
1207
+ textfont={"color": color},
1208
+ marker={
1209
+ "color": color,
1210
+ "size": float(SCATTER_CONFIG.get("marker_size", 60)) ** 0.5,
1211
+ "line": {
1212
+ "color": _color(
1213
+ SCATTER_CONFIG.get("edge_color", "#333333")
1214
+ ),
1215
+ "width": float(SCATTER_CONFIG.get("edge_width", 0.8)),
1216
+ },
1217
+ },
1218
+ hovertemplate="%{text}: %{x}, %{y}<extra></extra>",
1219
+ )
1220
+ )
1221
+ fig.update_layout(
1222
+ xaxis={
1223
+ "range": list(chart.limits(rows["x"], "x")),
1224
+ "title_text": chart.xlabel,
1225
+ "ticksuffix": chart.x_fmt.units,
1226
+ "showgrid": True,
1227
+ },
1228
+ yaxis={
1229
+ "range": list(chart.limits(rows["y"], "y")),
1230
+ "title_text": chart.ylabel,
1231
+ "ticksuffix": chart.y_fmt.units,
1232
+ },
1233
+ )
1234
+ _legend_side(fig, chart.legend)
1235
+ return fig