tesorotools-python 0.1.7__tar.gz → 0.1.9__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 (172) hide show
  1. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/PKG-INFO +1 -1
  2. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/_common.py +358 -11
  3. tesorotools_python-0.1.9/src/tesorotools/artists/_dates.py +238 -0
  4. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/box_plot.py +2 -0
  5. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/line_plot.py +54 -16
  6. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/plotly_backend.py +17 -0
  7. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/stacked.py +22 -7
  8. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/plots.yaml +27 -0
  9. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/lint.py +33 -11
  10. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/__init__.py +28 -1
  11. tesorotools_python-0.1.9/src/tesorotools/presentation/builder.py +2172 -0
  12. tesorotools_python-0.1.9/src/tesorotools/presentation/charts.py +101 -0
  13. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/cli.py +3 -5
  14. tesorotools_python-0.1.9/src/tesorotools/presentation/fit.py +147 -0
  15. tesorotools_python-0.1.9/src/tesorotools/presentation/glossary.py +154 -0
  16. tesorotools_python-0.1.9/src/tesorotools/presentation/lint.py +790 -0
  17. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/markdown.py +450 -45
  18. tesorotools_python-0.1.9/src/tesorotools/presentation/native.py +258 -0
  19. tesorotools_python-0.1.9/src/tesorotools/presentation/sections.py +71 -0
  20. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/skeleton.md +2 -2
  21. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/skill/SKILL.md +156 -30
  22. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/skill/reglas.md +5 -3
  23. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/providers/lseg.py +42 -14
  24. tesorotools_python-0.1.7/src/tesorotools/presentation/builder.py +0 -1175
  25. tesorotools_python-0.1.7/src/tesorotools/presentation/charts.py +0 -61
  26. tesorotools_python-0.1.7/src/tesorotools/presentation/lint.py +0 -183
  27. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/.gitignore +0 -0
  28. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/pyproject.toml +0 -0
  29. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/__init__.py +0 -0
  30. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/_build_context.py +0 -0
  31. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/_registry.py +0 -0
  32. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/__init__.py +0 -0
  33. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/bar_line.py +0 -0
  34. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/barh_plot.py +0 -0
  35. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/compact.py +0 -0
  36. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/intraday_plot.py +0 -0
  37. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/matrix.py +0 -0
  38. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/sankey.py +0 -0
  39. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/scatter.py +0 -0
  40. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/sharing.py +0 -0
  41. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/shock_plot.py +0 -0
  42. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/tikz_backend.py +0 -0
  43. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/type_curve.py +0 -0
  44. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/vector_plot.py +0 -0
  45. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/artists/waterfall.py +0 -0
  46. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/README.md +0 -0
  47. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
  48. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
  49. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
  50. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
  51. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
  52. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
  53. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
  54. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
  55. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/fonts/README.md +0 -0
  56. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/nota_template.dotx +0 -0
  57. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/template.docx +0 -0
  58. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/assets/tesoro.mplstyle +0 -0
  59. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/data_sources/__init__.py +0 -0
  60. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/data_sources/debug.py +0 -0
  61. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/database/__init__.py +0 -0
  62. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/database/local.py +0 -0
  63. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/database/push.py +0 -0
  64. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/database/shared.py +0 -0
  65. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/dependencies/__init__.py +0 -0
  66. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/dependencies/node.py +0 -0
  67. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/dependencies/resolution.py +0 -0
  68. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/driver.py +0 -0
  69. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/__init__.py +0 -0
  70. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/cli.py +0 -0
  71. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/scaffold.py +0 -0
  72. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/article/main.tex.tmpl +0 -0
  73. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/article/sections/01-introduccion.tex +0 -0
  74. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/article/sections/02-analisis.tex +0 -0
  75. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/article/sections/03-conclusiones.tex +0 -0
  76. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/beamer/main.tex.tmpl +0 -0
  77. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/beamer/slides/01-contexto.tex +0 -0
  78. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/beamer/slides/02-analisis.tex +0 -0
  79. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/beamer/slides/03-conclusiones.tex +0 -0
  80. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/beamer/style/beamerthemeTesoro.sty +0 -0
  81. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/book/appendices/a-fuentes.tex +0 -0
  82. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/book/chapters/01-introduccion.tex +0 -0
  83. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/book/chapters/02-analisis.tex +0 -0
  84. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/book/frontmatter/resumen.tex +0 -0
  85. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/book/main.tex.tmpl +0 -0
  86. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/README.md.tmpl +0 -0
  87. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/dot-gitignore +0 -0
  88. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/dot-vscode/settings.json +0 -0
  89. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/figures/dot-gitkeep +0 -0
  90. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/fonts/README.md +0 -0
  91. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/latexmkrc +0 -0
  92. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/refs.bib +0 -0
  93. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/style/tesoro.sty +0 -0
  94. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/style/tesorobrand.sty.tmpl +0 -0
  95. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/latex/templates/common/style/tesorolocal.sty.tmpl +0 -0
  96. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/manifest.py +0 -0
  97. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/__init__.py +0 -0
  98. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/builder.py +0 -0
  99. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/charts.py +0 -0
  100. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/cli.py +0 -0
  101. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/inline.py +0 -0
  102. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/issues.py +0 -0
  103. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/markdown.py +0 -0
  104. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/pdf.py +0 -0
  105. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/skeleton.md +0 -0
  106. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/skill/SKILL.md +0 -0
  107. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/skill/reglas.md +0 -0
  108. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/note/template.py +0 -0
  109. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/offsets/__init__.py +0 -0
  110. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/offsets/offsets.py +0 -0
  111. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/offsets/outliers.py +0 -0
  112. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/orchestration.py +0 -0
  113. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/palette.py +0 -0
  114. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/pipeline/__init__.py +0 -0
  115. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/pipeline/diagnose.py +0 -0
  116. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/pipeline/engine.py +0 -0
  117. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/pipeline/rules.py +0 -0
  118. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/house.py +0 -0
  119. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/pdf.py +0 -0
  120. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/presentation/template.py +0 -0
  121. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/providers/__init__.py +0 -0
  122. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/providers/base.py +0 -0
  123. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/providers/bde.py +0 -0
  124. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/providers/ecb.py +0 -0
  125. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/providers/fred.py +0 -0
  126. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/providers/imf_irfcl.py +0 -0
  127. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/py.typed +0 -0
  128. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/__init__.py +0 -0
  129. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/__init__.py +0 -0
  130. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/content.py +0 -0
  131. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/images.py +0 -0
  132. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/section.py +0 -0
  133. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/subtitle.py +0 -0
  134. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/table.py +0 -0
  135. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/text.py +0 -0
  136. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/content/title.py +0 -0
  137. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/document.py +0 -0
  138. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/render/report.py +0 -0
  139. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/scaffold.py +0 -0
  140. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/testing/__init__.py +0 -0
  141. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/testing/compare.py +0 -0
  142. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/__init__.py +0 -0
  143. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/config.py +0 -0
  144. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/format.py +0 -0
  145. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/globals.py +0 -0
  146. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/matplotlib.py +0 -0
  147. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/series.py +0 -0
  148. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/shortcuts.py +0 -0
  149. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/utils/template.py +0 -0
  150. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/__init__.py +0 -0
  151. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/app.py +0 -0
  152. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/brand.py +0 -0
  153. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/cli.py +0 -0
  154. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/components.py +0 -0
  155. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/formatting.py +0 -0
  156. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/launcher.py +0 -0
  157. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/palette.py +0 -0
  158. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/plotting.py +0 -0
  159. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/scaffold.py +0 -0
  160. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/Dockerfile.tmpl +0 -0
  161. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/README.md.tmpl +0 -0
  162. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/dot-dockerignore.tmpl +0 -0
  163. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/dot-gitignore.tmpl +0 -0
  164. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/dot-streamlit/config.toml.tmpl +0 -0
  165. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/pyproject.toml.tmpl +0 -0
  166. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/src/$package/__init__.py.tmpl +0 -0
  167. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/src/$package/cli.py.tmpl +0 -0
  168. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/src/$package/main.py.tmpl +0 -0
  169. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/src/$package/sections.py.tmpl +0 -0
  170. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/tests/__init__.py.tmpl +0 -0
  171. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/templates/tests/test_app.py.tmpl +0 -0
  172. {tesorotools_python-0.1.7 → tesorotools_python-0.1.9}/src/tesorotools/web/theme.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tesorotools-python
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Requires-Python: >=3.13
5
5
  Requires-Dist: babel>=2.17
6
6
  Requires-Dist: matplotlib>=3.10
@@ -47,9 +47,21 @@ from matplotlib.dates import (
47
47
  from matplotlib.figure import Figure
48
48
  from matplotlib.font_manager import FontProperties
49
49
  from matplotlib.legend import Legend as MplLegend
50
- from matplotlib.ticker import FixedLocator, FuncFormatter, MaxNLocator
50
+ from matplotlib.text import Text
51
+ from matplotlib.ticker import (
52
+ FixedFormatter,
53
+ FixedLocator,
54
+ FuncFormatter,
55
+ MaxNLocator,
56
+ NullFormatter,
57
+ )
51
58
  from yaml.nodes import MappingNode
52
59
 
60
+ from tesorotools.artists._dates import (
61
+ DateLayout,
62
+ date_layouts,
63
+ period_label,
64
+ )
53
65
  from tesorotools.utils.matplotlib import (
54
66
  PLOT_CONFIG,
55
67
  apply_house_style,
@@ -110,6 +122,53 @@ def date_maxticks(density: str, *, compact: bool) -> int:
110
122
  return int(table.get(density, fallback[density]))
111
123
 
112
124
 
125
+ # Most date labels an axis shows per date_density level, read from
126
+ # ``compact.date_max_labels`` / ``line.date_max_labels`` in ``plots.yaml``:
127
+ # the finest calendar step (every year, every month...) whose labels fit in
128
+ # the axis width AND stay within this count wins.
129
+ _DATE_MAX_LABELS_FALLBACK_COMPACT: dict[str, int] = {
130
+ "few": 4,
131
+ "default": 8,
132
+ "many": 14,
133
+ }
134
+ _DATE_MAX_LABELS_FALLBACK_LINE: dict[str, int] = {
135
+ "few": 6,
136
+ "default": 15,
137
+ "many": 30,
138
+ }
139
+
140
+
141
+ def date_max_labels(density: str, *, compact: bool) -> int:
142
+ """Most date labels a ``date_density`` level allows on the x-axis.
143
+
144
+ Reads ``compact.date_max_labels`` / ``line.date_max_labels`` from
145
+ ``plots.yaml``, falling back to the built-in table when the config
146
+ (or a level) is absent.
147
+ """
148
+ cfg = COMPACT_CONFIG if compact else LINE_CONFIG
149
+ fallback = (
150
+ _DATE_MAX_LABELS_FALLBACK_COMPACT
151
+ if compact
152
+ else _DATE_MAX_LABELS_FALLBACK_LINE
153
+ )
154
+ table: dict[str, Any] = cfg.get("date_max_labels", fallback)
155
+ return int(table.get(density, fallback[density]))
156
+
157
+
158
+ def _date_axis_config(*, compact: bool) -> tuple[int, float, float]:
159
+ """Calendar date-axis knobs of a mode, from ``plots.yaml``.
160
+
161
+ ``(date_monthly_max_months, date_centered_min_fraction,
162
+ date_label_gap_em)``.
163
+ """
164
+ cfg = COMPACT_CONFIG if compact else LINE_CONFIG
165
+ return (
166
+ int(cfg.get("date_monthly_max_months", 24)),
167
+ float(cfg.get("date_centered_min_fraction", 0.5)),
168
+ float(cfg.get("date_label_gap_em", 0.3)),
169
+ )
170
+
171
+
113
172
  def dynamic_dpi(fig: Figure) -> int:
114
173
  """Save DPI adapted to the figure's physical size.
115
174
 
@@ -344,9 +403,18 @@ class Legend:
344
403
  values work in compact and in full-size charts, so a
345
404
  compact chart in a Word table can carry its labels under
346
405
  the plot when the text above it already reads as a title.
406
+ ``font_pt`` fixes the size of the legend text (a slide wants it
407
+ as big as the ticks) and ``min_entries=2`` drops the legend of a
408
+ chart with a single series.
347
409
  """
348
410
 
349
- def __init__(self, ncol: int | None = None, position: str = "auto") -> None:
411
+ def __init__(
412
+ self,
413
+ ncol: int | None = None,
414
+ position: str = "auto",
415
+ font_pt: float | None = None,
416
+ min_entries: int = 1,
417
+ ) -> None:
350
418
  if position not in LEGEND_POSITIONS:
351
419
  raise ValueError(
352
420
  f"legend position must be one of {LEGEND_POSITIONS}: "
@@ -354,6 +422,12 @@ class Legend:
354
422
  )
355
423
  self.ncol = ncol
356
424
  self.position = position
425
+ #: Size of the legend text (points); ``None`` keeps the house
426
+ #: size (``compact.legend_font_pt``, never above the ticks).
427
+ self.font_pt = font_pt
428
+ #: Fewer entries than this draw no legend: ``2`` leaves a chart
429
+ #: of a single series without one.
430
+ self.min_entries = min_entries
357
431
 
358
432
  @classmethod
359
433
  def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
@@ -804,6 +878,188 @@ def _date_label_ha(
804
878
  return "right" if rotation > 0 else "left"
805
879
 
806
880
 
881
+ def _style_date_labels(ax: Axes, rot: float, *, minor: bool) -> None:
882
+ """Tilt the date labels *rot* degrees with the matching anchor.
883
+
884
+ Rotation goes in the axis tick kwargs; ``ha`` and the anchor rotation
885
+ mode are per-label Text properties, so they are reapplied after every
886
+ draw or locator swap.
887
+ """
888
+ ha = _date_label_ha(None if rot == 0 else rot)
889
+ ax.tick_params( # type: ignore[reportUnknownMemberType]
890
+ axis="x", which="minor" if minor else "major", rotation=rot
891
+ )
892
+ for label in ax.get_xticklabels(minor=minor):
893
+ label.set_horizontalalignment(ha)
894
+ if rot != 0:
895
+ label.set_rotation_mode("anchor")
896
+
897
+
898
+ def _layout_texts(layout: DateLayout, *, pin_start: bool) -> list[str]:
899
+ """The label texts of *layout*, as the axis will print them."""
900
+ if layout.centered:
901
+ # centred: each label names its period
902
+ return [
903
+ period_label(p, layout.step, first=i == 0)
904
+ for i, p in enumerate(layout.periods)
905
+ ]
906
+ values = [float(cast(Any, date2num(t))) for t in layout.ticks]
907
+ formatter = _tick_formatter(FixedLocator(values), values[0], pin_start)
908
+ return list(formatter.format_ticks(values))
909
+
910
+
911
+ def _tick_formatter(
912
+ locator: FixedLocator, start: float, pin_start: bool
913
+ ) -> ConciseDateFormatter:
914
+ return (
915
+ _StartLabeledConcise(locator, start)
916
+ if pin_start
917
+ else ConciseDateFormatter(locator)
918
+ )
919
+
920
+
921
+ def calendar_date_axis(
922
+ ax: Axes,
923
+ layouts: list[DateLayout],
924
+ *,
925
+ pin_start: bool,
926
+ rotation: float | None = None,
927
+ auto_rotate: bool = False,
928
+ gap_em: float = 0.3,
929
+ ) -> DateLayout:
930
+ """Label every year (or month) the axis can hold.
931
+
932
+ *layouts* are the candidate calendar steps, finest first (see
933
+ :func:`tesorotools.artists._dates.date_layouts`). Each one's labels
934
+ are measured at their real size and position, and the first whose
935
+ labels do not overlap wins: a decade shows every year, thirty years
936
+ fall back to one every two or five. When none fits, the coarsest is
937
+ used. Returns the layout drawn.
938
+
939
+ With the start pinned, the first calendar tick gives way to the
940
+ (wider) start label when they collide and it lies under half a step
941
+ from the start: a series from October 2015 shows ``oct. 2015``,
942
+ 2017, 2018... Further away, dropping it would read as a skipped
943
+ year, so that step counts as not fitting.
944
+
945
+ Centred layouts (``date_labels: centered``) put the tick marks on the
946
+ period boundaries and the labels, as minor ticks without a mark, in
947
+ the middle of each period.
948
+
949
+ ``rotation`` tilts the labels. ``auto_rotate`` (ignored when a
950
+ rotation is given) tries 0, 30, 45, 60 and 90 degrees and keeps the
951
+ flattest tilt that reaches the finest step any of them reaches.
952
+
953
+ Neighbouring labels must stay *gap_em* ems apart
954
+ (``date_label_gap_em`` in ``plots.yaml``).
955
+ """
956
+ fig = ax.get_figure()
957
+ assert fig is not None
958
+ # settle the layout so dates map to their final pixels
959
+ fig.canvas.draw() # type: ignore[reportUnknownMemberType]
960
+ renderer = fig.canvas.get_renderer() # type: ignore[reportUnknownMemberType]
961
+ font = ax.xaxis.get_major_ticks()[0].label1.get_fontproperties()
962
+ # a fixed share of an em between neighbouring labels, whatever the dpi
963
+ pad_px = (
964
+ float(
965
+ renderer.points_to_pixels(font.get_size_in_points()) # type: ignore[reportUnknownMemberType]
966
+ )
967
+ * gap_em
968
+ )
969
+
970
+ def _boxes(layout: DateLayout, rot: float) -> list[Any]:
971
+ texts = _layout_texts(layout, pin_start=pin_start)
972
+ ha = _date_label_ha(None if rot == 0 else rot)
973
+ boxes: list[Any] = []
974
+ for when, text in zip(layout.labels, texts):
975
+ x_px = float(
976
+ ax.transData.transform((float(cast(Any, date2num(when))), 0))[0]
977
+ )
978
+ probe = Text(
979
+ x_px,
980
+ 0.0,
981
+ text,
982
+ fontproperties=font,
983
+ rotation=rot,
984
+ horizontalalignment=ha,
985
+ rotation_mode="anchor" if rot != 0 else "default",
986
+ )
987
+ probe.set_figure(fig)
988
+ boxes.append(probe.get_window_extent(renderer)) # type: ignore[reportUnknownArgumentType]
989
+ return boxes
990
+
991
+ def _fit(layout: DateLayout, rot: float) -> list[int] | None:
992
+ boxes = _boxes(layout, rot)
993
+ kept = list(range(len(boxes)))
994
+ centered = layout.centered
995
+ if pin_start and not centered and len(kept) > 2:
996
+ # the first calendar tick may give way to the (wider) start
997
+ # label only when it sits under half a step from the start:
998
+ # further away, dropping it would open a gap that reads as a
999
+ # skipped year, so the step does not fit instead
1000
+ when = [float(cast(Any, date2num(t))) for t in layout.ticks]
1001
+ near = when[1] - when[0] < (when[2] - when[1]) / 2
1002
+ if near and boxes[1].x0 <= boxes[0].x1 + pad_px:
1003
+ kept.pop(1)
1004
+ clear = all(
1005
+ boxes[b].x0 > boxes[a].x1 + pad_px for a, b in zip(kept, kept[1:])
1006
+ )
1007
+ return kept if clear else None
1008
+
1009
+ tilts = (
1010
+ (0.0, 30.0, 45.0, 60.0, 90.0)
1011
+ if auto_rotate and rotation is None
1012
+ else (0.0 if rotation is None else float(rotation),)
1013
+ )
1014
+ best: tuple[int, float, list[int]] | None = None
1015
+ for rot in tilts:
1016
+ for i, layout in enumerate(layouts):
1017
+ if best is not None and i >= best[0]:
1018
+ break
1019
+ kept = _fit(layout, rot)
1020
+ if kept is not None:
1021
+ best = (i, rot, kept)
1022
+ break
1023
+ if best is not None and best[0] == 0:
1024
+ break
1025
+ if best is None:
1026
+ coarsest = layouts[-1]
1027
+ best = (len(layouts) - 1, tilts[0], list(range(len(coarsest.labels))))
1028
+ index, rot, kept = best
1029
+ layout = layouts[index]
1030
+ centered = layout.centered
1031
+
1032
+ if centered:
1033
+ major = FixedLocator(
1034
+ [float(cast(Any, date2num(t))) for t in layout.ticks]
1035
+ )
1036
+ ax.xaxis.set_major_locator(major)
1037
+ ax.xaxis.set_major_formatter(NullFormatter())
1038
+ texts = _layout_texts(layout, pin_start=pin_start)
1039
+ ax.xaxis.set_minor_locator(
1040
+ FixedLocator(
1041
+ [float(cast(Any, date2num(layout.labels[k]))) for k in kept]
1042
+ )
1043
+ )
1044
+ ax.xaxis.set_minor_formatter(FixedFormatter([texts[k] for k in kept]))
1045
+ ax.tick_params( # type: ignore[reportUnknownMemberType]
1046
+ axis="x",
1047
+ which="minor",
1048
+ length=0,
1049
+ labelsize=ax.xaxis.get_major_ticks()[0].label1.get_fontsize(),
1050
+ )
1051
+ else:
1052
+ values = [float(cast(Any, date2num(layout.ticks[k]))) for k in kept]
1053
+ fixed = FixedLocator(values)
1054
+ ax.xaxis.set_major_locator(fixed)
1055
+ ax.xaxis.set_major_formatter(
1056
+ _tick_formatter(fixed, values[0], pin_start)
1057
+ )
1058
+ fig.canvas.draw() # type: ignore[reportUnknownMemberType]
1059
+ _style_date_labels(ax, rot, minor=centered)
1060
+ return layout
1061
+
1062
+
807
1063
  def compact_date_axis(
808
1064
  ax: Axes,
809
1065
  data_min: Any,
@@ -813,8 +1069,22 @@ def compact_date_axis(
813
1069
  rotation: float | None = None,
814
1070
  pin_start: bool | None = None,
815
1071
  auto_rotate: bool = False,
1072
+ max_labels: int | None = None,
1073
+ step: str = "auto",
1074
+ labels: str = "tick",
816
1075
  ) -> None:
817
- """Compact date x-axis: few ticks, anchored to the data span.
1076
+ """Compact date x-axis: as many calendar dates as fit the narrow figure.
1077
+
1078
+ The axis labels every year (or, over two years at most, every month)
1079
+ that fits in the width without overlapping and within *max_labels*
1080
+ (``None`` reads ``compact.date_max_labels`` for the default density),
1081
+ stepping to one every 2, 5, 10... years only when they do not fit;
1082
+ ``step`` (``"1Y"``, ``"3M"``...) fixes the step instead, and
1083
+ ``labels="centered"`` centres each year over its stretch. See
1084
+ :func:`calendar_date_axis`.
1085
+
1086
+ Spans too short for a calendar grid (a few weeks of daily data) keep
1087
+ the historical behaviour described below, with *maxticks* ticks.
818
1088
 
819
1089
  Ticks are computed over the **data span** ``[data_min,
820
1090
  data_max]`` and pinned with a ``FixedLocator``, so none
@@ -853,6 +1123,31 @@ def compact_date_axis(
853
1123
  if pin_start is None
854
1124
  else pin_start
855
1125
  )
1126
+ monthly_max, min_fraction, gap_em = _date_axis_config(compact=True)
1127
+ layouts = date_layouts(
1128
+ data_min,
1129
+ data_max,
1130
+ step=step,
1131
+ max_labels=(
1132
+ date_max_labels("default", compact=True)
1133
+ if max_labels is None
1134
+ else max_labels
1135
+ ),
1136
+ monthly_max_months=monthly_max,
1137
+ pin_start=show_start,
1138
+ centered=labels == "centered",
1139
+ min_fraction=min_fraction,
1140
+ )
1141
+ if layouts:
1142
+ calendar_date_axis(
1143
+ ax,
1144
+ layouts,
1145
+ pin_start=show_start,
1146
+ rotation=rotation,
1147
+ auto_rotate=auto_rotate,
1148
+ gap_em=gap_em,
1149
+ )
1150
+ return
856
1151
  # same span-clipped ticks (and same pinned start) the non-compact axis
857
1152
  # uses, so both modes answer "when does this start?" identically
858
1153
  ticks: list[float] = date_span_ticks(
@@ -958,8 +1253,20 @@ def apply_date_axis(
958
1253
  data_min: Any = None,
959
1254
  data_max: Any = None,
960
1255
  pin_start: bool = False,
1256
+ max_labels: int | None = None,
1257
+ step: str = "auto",
1258
+ labels: str = "tick",
961
1259
  ) -> None:
962
- """Native matplotlib date x-axis with a soft tick count and optional slant.
1260
+ """Native matplotlib date x-axis with every year (or month) that fits.
1261
+
1262
+ Given the ``data_min`` / ``data_max`` span, the axis labels every year
1263
+ (or, over two years at most, every month) that fits in the width and
1264
+ within *max_labels* (``None`` reads ``line.date_max_labels`` for the
1265
+ default density), stepping to one every 2, 5, 10... years only when
1266
+ they do not; ``step`` fixes the step and ``labels="centered"`` centres
1267
+ each year over its stretch (see :func:`calendar_date_axis`). The rest
1268
+ of this docstring describes the fallback for spans too short for a
1269
+ calendar grid, or when no span is given.
963
1270
 
964
1271
  The non-compact counterpart to :func:`compact_date_axis`: it takes over the
965
1272
  x-axis (which must carry a **native** date axis, i.e. the series were
@@ -984,6 +1291,31 @@ def apply_date_axis(
984
1291
  ``rotation=None`` has to *actively* flatten them to keep the documented
985
1292
  "horizontal unless asked" behaviour.
986
1293
  """
1294
+ if data_min is not None and data_max is not None:
1295
+ monthly_max, min_fraction, gap_em = _date_axis_config(compact=False)
1296
+ layouts = date_layouts(
1297
+ data_min,
1298
+ data_max,
1299
+ step=step,
1300
+ max_labels=(
1301
+ date_max_labels("default", compact=False)
1302
+ if max_labels is None
1303
+ else max_labels
1304
+ ),
1305
+ monthly_max_months=monthly_max,
1306
+ pin_start=pin_start,
1307
+ centered=labels == "centered",
1308
+ min_fraction=min_fraction,
1309
+ )
1310
+ if layouts:
1311
+ calendar_date_axis(
1312
+ ax,
1313
+ layouts,
1314
+ pin_start=pin_start,
1315
+ rotation=rotation,
1316
+ gap_em=gap_em,
1317
+ )
1318
+ return
987
1319
  if pin_start and data_min is not None and data_max is not None:
988
1320
  ticks = date_span_ticks(
989
1321
  data_min, data_max, maxticks=maxticks, pin_start=True
@@ -1226,8 +1558,9 @@ def style_compact_axes(
1226
1558
  :func:`declutter_spines`.
1227
1559
  """
1228
1560
  if font_pt is not None:
1561
+ # which="both": centred date labels live on the minor ticks
1229
1562
  ax.tick_params( # type: ignore[reportUnknownMemberType]
1230
- axis="both", labelsize=font_pt
1563
+ axis="both", which="both", labelsize=font_pt
1231
1564
  )
1232
1565
  if side == "right":
1233
1566
  ax.yaxis.tick_right()
@@ -1244,6 +1577,8 @@ def place_legend(
1244
1577
  ncol: int | None = None,
1245
1578
  position: str = "auto",
1246
1579
  entries: tuple[list[Any], list[str]] | None = None,
1580
+ font_pt: float | None = None,
1581
+ min_entries: int = 1,
1247
1582
  ) -> MplLegend | None:
1248
1583
  """Place the figure legend, compact-aware, from the labelled artists.
1249
1584
 
@@ -1257,17 +1592,21 @@ def place_legend(
1257
1592
  ``entries`` passes precomputed ``(handles, labels)`` instead of reading
1258
1593
  them off *ax* -- for charts whose series span several axes (a
1259
1594
  ``twinx`` secondary y-axis), in the order the legend should list them.
1260
- Returns ``None`` (drawing nothing) when there are no labels.
1595
+ ``font_pt`` sets the size of the legend text. Returns ``None``
1596
+ (drawing nothing) when there are no labels, or fewer than
1597
+ ``min_entries``.
1261
1598
  """
1262
1599
  handles, labels = (
1263
1600
  entries if entries is not None else ax.get_legend_handles_labels()
1264
1601
  )
1265
- if not labels:
1602
+ if not labels or len(labels) < min_entries:
1266
1603
  return None
1267
1604
  side = resolve_legend_position(position, compact=compact)
1268
1605
  fig_w_px: float = fig.get_size_inches()[0] * fig.dpi
1269
1606
  if compact:
1270
1607
  legend_kw = compact_legend_kwargs(ax)
1608
+ if font_pt is not None:
1609
+ legend_kw["fontsize"] = font_pt
1271
1610
  if ncol is None:
1272
1611
  ncol = auto_ncol(
1273
1612
  ax, labels, available_width_px=fig_w_px, **legend_kw
@@ -1285,8 +1624,12 @@ def place_legend(
1285
1624
  **legend_kw,
1286
1625
  )
1287
1626
  if ncol is None:
1288
- ncol = auto_ncol(ax, labels, available_width_px=fig_w_px)
1289
- return legend_band(ax, handles, labels, ncol=ncol, position=side)
1627
+ ncol = auto_ncol(
1628
+ ax, labels, available_width_px=fig_w_px, fontsize=font_pt
1629
+ )
1630
+ return legend_band(
1631
+ ax, handles, labels, ncol=ncol, position=side, fontsize=font_pt
1632
+ )
1290
1633
 
1291
1634
 
1292
1635
  def legend_layout(legend: Legend | None) -> dict[str, Any]:
@@ -1302,9 +1645,13 @@ def legend_layout(legend: Legend | None) -> dict[str, Any]:
1302
1645
  fig, ax, compact=self.compact, **legend_layout(self.legend)
1303
1646
  )
1304
1647
  """
1648
+ if legend is None:
1649
+ return {"ncol": None, "position": "auto"}
1305
1650
  return {
1306
- "ncol": legend.ncol if legend is not None else None,
1307
- "position": legend.position if legend is not None else "auto",
1651
+ "ncol": legend.ncol,
1652
+ "position": legend.position,
1653
+ "font_pt": legend.font_pt,
1654
+ "min_entries": legend.min_entries,
1308
1655
  }
1309
1656
 
1310
1657