tesorotools-python 0.1.9__tar.gz → 0.1.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/.gitignore +2 -1
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/PKG-INFO +1 -1
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/pyproject.toml +4 -1
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/matrix.py +36 -9
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/plotly_backend.py +16 -11
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/waterfall.py +46 -29
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/lint.py +53 -12
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/markdown.py +43 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/palette.py +2 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/builder.py +481 -118
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/charts.py +15 -7
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/house.py +13 -3
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/lint.py +594 -36
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/markdown.py +29 -5
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/native.py +19 -2
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/skill/SKILL.md +81 -36
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/matplotlib.py +10 -1
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/__init__.py +14 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/app.py +190 -3
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/brand.py +77 -5
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/components.py +24 -7
- tesorotools_python-0.1.11/src/tesorotools/web/period.py +220 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/theme.py +8 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/_build_context.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/_registry.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/_common.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/_dates.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/bar_line.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/box_plot.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/compact.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/intraday_plot.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/line_plot.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/sankey.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/scatter.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/sharing.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/shock_plot.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/stacked.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/tikz_backend.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/type_curve.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/vector_plot.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/nota_template.dotx +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/plots.yaml +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/template.docx +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/driver.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/cli.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/scaffold.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/article/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/article/sections/01-introduccion.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/article/sections/02-analisis.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/article/sections/03-conclusiones.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/beamer/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/beamer/slides/01-contexto.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/beamer/slides/02-analisis.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/beamer/slides/03-conclusiones.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/beamer/style/beamerthemeTesoro.sty +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/book/appendices/a-fuentes.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/book/chapters/01-introduccion.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/book/chapters/02-analisis.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/book/frontmatter/resumen.tex +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/book/main.tex.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/README.md.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/dot-gitignore +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/dot-vscode/settings.json +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/figures/dot-gitkeep +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/fonts/README.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/latexmkrc +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/refs.bib +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/style/tesoro.sty +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/style/tesorobrand.sty.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/latex/templates/common/style/tesorolocal.sty.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/builder.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/charts.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/cli.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/inline.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/issues.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/pdf.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/skeleton.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/skill/SKILL.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/skill/reglas.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/note/template.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/orchestration.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/cli.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/fit.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/glossary.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/pdf.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/sections.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/skeleton.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/skill/reglas.md +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/presentation/template.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/providers/fred.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/providers/imf_irfcl.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/providers/lseg.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/table.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/document.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/scaffold.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/utils/template.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/cli.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/formatting.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/launcher.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/palette.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/plotting.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/scaffold.py +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/Dockerfile.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/README.md.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/dot-dockerignore.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/dot-gitignore.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/dot-streamlit/config.toml.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/pyproject.toml.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/src/$package/__init__.py.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/src/$package/cli.py.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/src/$package/main.py.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/src/$package/sections.py.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/tests/__init__.py.tmpl +0 -0
- {tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/web/templates/tests/test_app.py.tmpl +0 -0
|
@@ -45,7 +45,10 @@ tesorotools-presentacion = "tesorotools.presentation.cli:main"
|
|
|
45
45
|
[dependency-groups]
|
|
46
46
|
dev = [
|
|
47
47
|
"ruff>=0.8",
|
|
48
|
-
|
|
48
|
+
# Con `[nodejs]` node llega como rueda de PyPI; sin él pyright lo baja con
|
|
49
|
+
# nodeenv en cada job, y nodeenv 1.10 se rompió el 03/11/2026 con una
|
|
50
|
+
# versión `-alpha` de node.
|
|
51
|
+
"pyright[nodejs]>=1.1",
|
|
49
52
|
"pre-commit>=4.0",
|
|
50
53
|
"pandas-stubs>=2.2",
|
|
51
54
|
"coverage>=7.0",
|
|
@@ -17,8 +17,10 @@ column per column id -- the same wide shape
|
|
|
17
17
|
:class:`GroupedBarChart` consumes -- so ``rows`` selects and
|
|
18
18
|
labels the index and ``cols`` selects and labels the columns.
|
|
19
19
|
Cell shading uses a sequential colormap normalised over the
|
|
20
|
-
selected values
|
|
21
|
-
|
|
20
|
+
selected values or, when they have both signs, a diverging one
|
|
21
|
+
centred on zero (:data:`DIVERGING`), so -0,6 reads as strongly as
|
|
22
|
+
0,6 instead of almost white; text colour flips to stay legible on
|
|
23
|
+
dark cells. ``NaN`` cells are left blank (no fill, no text), which
|
|
22
24
|
covers triangular or sparse matrices.
|
|
23
25
|
|
|
24
26
|
Styling constants come from ``PLOT_CONFIG['matrix']``.
|
|
@@ -34,11 +36,12 @@ import matplotlib.pyplot as plt
|
|
|
34
36
|
import numpy as np
|
|
35
37
|
import pandas as pd
|
|
36
38
|
from matplotlib.axes import Axes
|
|
37
|
-
from matplotlib.colors import Colormap, Normalize
|
|
39
|
+
from matplotlib.colors import Colormap, LinearSegmentedColormap, Normalize
|
|
38
40
|
from matplotlib.figure import Figure
|
|
39
41
|
from matplotlib.patches import Rectangle
|
|
40
42
|
from yaml.nodes import MappingNode
|
|
41
43
|
|
|
44
|
+
from tesorotools import palette
|
|
42
45
|
from tesorotools.artists._common import (
|
|
43
46
|
FigureLifecycle,
|
|
44
47
|
FIG_CONFIG,
|
|
@@ -51,6 +54,12 @@ from tesorotools.utils.template import TemplateLoader
|
|
|
51
54
|
__all__ = ["MatrixChart"]
|
|
52
55
|
|
|
53
56
|
MATRIX_CONFIG: dict[str, Any] = PLOT_CONFIG["matrix"]
|
|
57
|
+
#: The scale of a matrix with figures of both signs: red under zero,
|
|
58
|
+
#: white at zero and blue over it, in the house colours.
|
|
59
|
+
DIVERGING_STOPS = (f"#{palette.RED}", "#FFFFFF", f"#{palette.BLUE}")
|
|
60
|
+
DIVERGING: Colormap = LinearSegmentedColormap.from_list(
|
|
61
|
+
"tesoro_diverging", list(DIVERGING_STOPS)
|
|
62
|
+
)
|
|
54
63
|
|
|
55
64
|
|
|
56
65
|
def _text_color(rgba: tuple[float, float, float, float]) -> str:
|
|
@@ -91,10 +100,12 @@ class MatrixChart(FigureLifecycle):
|
|
|
91
100
|
:class:`Format` for the big per-cell value.
|
|
92
101
|
cmap
|
|
93
102
|
Matplotlib colormap name for the shading (default
|
|
94
|
-
``PLOT_CONFIG['matrix']['cmap']``
|
|
103
|
+
``PLOT_CONFIG['matrix']['cmap']`` or, with values of both
|
|
104
|
+
signs, :data:`DIVERGING`).
|
|
95
105
|
vmin, vmax
|
|
96
106
|
Value range mapped to the colormap ends. Default to
|
|
97
|
-
the min / max of the selected values
|
|
107
|
+
the min / max of the selected values (with both signs,
|
|
108
|
+
symmetric around zero).
|
|
98
109
|
xlabel, ylabel
|
|
99
110
|
Axis titles (the column / row variable names).
|
|
100
111
|
figsize
|
|
@@ -142,6 +153,8 @@ class MatrixChart(FigureLifecycle):
|
|
|
142
153
|
self.scale = scale
|
|
143
154
|
self.fmt = fmt or Format()
|
|
144
155
|
self.cmap = cmap or MATRIX_CONFIG["cmap"]
|
|
156
|
+
#: The colormap was not chosen: values of both signs diverge.
|
|
157
|
+
self.default_cmap = cmap is None
|
|
145
158
|
self.vmin = vmin
|
|
146
159
|
self.vmax = vmax
|
|
147
160
|
self.xlabel = xlabel
|
|
@@ -230,10 +243,7 @@ class MatrixChart(FigureLifecycle):
|
|
|
230
243
|
|
|
231
244
|
values_arr = matrix.to_numpy(dtype=float)
|
|
232
245
|
valid = values_arr[~np.isnan(values_arr)]
|
|
233
|
-
|
|
234
|
-
vmax = self.vmax if self.vmax is not None else float(valid.max())
|
|
235
|
-
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
236
|
-
cmap: Colormap = mpl.colormaps[self.cmap]
|
|
246
|
+
norm, cmap = self.shading(valid)
|
|
237
247
|
|
|
238
248
|
fig_kw = dict(FIG_CONFIG)
|
|
239
249
|
fig_kw["figsize"] = self.figsize if self.figsize is not None else (6, 6)
|
|
@@ -276,6 +286,23 @@ class MatrixChart(FigureLifecycle):
|
|
|
276
286
|
self._style_axes(ax, row_ids, col_ids)
|
|
277
287
|
return fig, ax
|
|
278
288
|
|
|
289
|
+
def shading(
|
|
290
|
+
self, valid: np.ndarray[Any, np.dtype[np.float64]]
|
|
291
|
+
) -> tuple[Normalize, Colormap]:
|
|
292
|
+
"""The colour scale of the cells with *valid* values.
|
|
293
|
+
|
|
294
|
+
With values of both signs, and no ``cmap``, ``vmin`` or ``vmax``
|
|
295
|
+
given, :data:`DIVERGING` centred on zero: a sequential scale
|
|
296
|
+
would paint -0,6 almost white, like a zero.
|
|
297
|
+
"""
|
|
298
|
+
vmin = self.vmin if self.vmin is not None else float(valid.min())
|
|
299
|
+
vmax = self.vmax if self.vmax is not None else float(valid.max())
|
|
300
|
+
both = vmin < 0 < vmax and self.vmin is None and self.vmax is None
|
|
301
|
+
if self.default_cmap and both:
|
|
302
|
+
edge = max(-vmin, vmax)
|
|
303
|
+
return Normalize(vmin=-edge, vmax=edge), DIVERGING
|
|
304
|
+
return Normalize(vmin=vmin, vmax=vmax), mpl.colormaps[self.cmap]
|
|
305
|
+
|
|
279
306
|
def _annotate_cell(
|
|
280
307
|
self,
|
|
281
308
|
ax: Axes,
|
{tesorotools_python-0.1.9 → tesorotools_python-0.1.11}/src/tesorotools/artists/plotly_backend.py
RENAMED
|
@@ -46,7 +46,6 @@ from __future__ import annotations
|
|
|
46
46
|
|
|
47
47
|
from typing import TYPE_CHECKING, Any
|
|
48
48
|
|
|
49
|
-
import matplotlib as mpl
|
|
50
49
|
import matplotlib.colors as mcolors
|
|
51
50
|
import matplotlib.pyplot as plt
|
|
52
51
|
import numpy as np
|
|
@@ -763,10 +762,8 @@ def waterfall_to_figure(chart: Waterfall) -> go.Figure:
|
|
|
763
762
|
dec_color = WATERFALL_CONFIG.get("decrease_color", "C9")
|
|
764
763
|
colors: list[str] = []
|
|
765
764
|
for i in range(n):
|
|
766
|
-
if i
|
|
767
|
-
colors.append(_color(start_color))
|
|
768
|
-
elif i == n - 1 or is_total[i]:
|
|
769
|
-
colors.append(_color(end_color))
|
|
765
|
+
if is_total[i]:
|
|
766
|
+
colors.append(_color(start_color if i == 0 else end_color))
|
|
770
767
|
else:
|
|
771
768
|
base = inc_color if values[i] >= 0 else dec_color
|
|
772
769
|
colors.append(_color(base, light_alpha))
|
|
@@ -919,17 +916,25 @@ def grouped_bar_to_figure(chart: GroupedBarChart) -> go.Figure:
|
|
|
919
916
|
|
|
920
917
|
def matrix_to_figure(chart: MatrixChart) -> go.Figure:
|
|
921
918
|
"""Interactive ``Figure`` from a :class:`MatrixChart` (heatmap + labels)."""
|
|
922
|
-
from tesorotools.artists.matrix import
|
|
919
|
+
from tesorotools.artists.matrix import (
|
|
920
|
+
DIVERGING,
|
|
921
|
+
DIVERGING_STOPS,
|
|
922
|
+
_text_color,
|
|
923
|
+
)
|
|
923
924
|
|
|
924
925
|
row_ids = list(chart.rows)
|
|
925
926
|
col_ids = list(chart.cols)
|
|
926
927
|
matrix = chart.data.loc[row_ids, col_ids] * chart.scale
|
|
927
928
|
values = matrix.to_numpy(dtype=float)
|
|
928
929
|
valid = values[~np.isnan(values)]
|
|
929
|
-
|
|
930
|
-
vmax =
|
|
931
|
-
|
|
932
|
-
|
|
930
|
+
norm, cmap = chart.shading(valid)
|
|
931
|
+
vmin, vmax = norm.vmin, norm.vmax
|
|
932
|
+
# the diverging scale of values of both signs, as in the PNG
|
|
933
|
+
colorscale: str | list[list[Any]] = (
|
|
934
|
+
[[n / 2, stop] for n, stop in enumerate(DIVERGING_STOPS)]
|
|
935
|
+
if cmap is DIVERGING
|
|
936
|
+
else "Blues"
|
|
937
|
+
)
|
|
933
938
|
|
|
934
939
|
row_labels = [chart.rows[r] for r in row_ids]
|
|
935
940
|
col_labels = [chart.cols[c] for c in col_ids]
|
|
@@ -954,7 +959,7 @@ def matrix_to_figure(chart: MatrixChart) -> go.Figure:
|
|
|
954
959
|
z=values,
|
|
955
960
|
x=col_labels,
|
|
956
961
|
y=row_labels,
|
|
957
|
-
colorscale=
|
|
962
|
+
colorscale=colorscale,
|
|
958
963
|
zmin=vmin,
|
|
959
964
|
zmax=vmax,
|
|
960
965
|
showscale=False,
|
|
@@ -25,6 +25,7 @@ import pandas as pd
|
|
|
25
25
|
from matplotlib.axes import Axes
|
|
26
26
|
from matplotlib.colors import to_rgba
|
|
27
27
|
from matplotlib.figure import Figure
|
|
28
|
+
from matplotlib.text import Annotation
|
|
28
29
|
from yaml.nodes import MappingNode
|
|
29
30
|
|
|
30
31
|
from tesorotools.artists._common import (
|
|
@@ -47,6 +48,31 @@ __all__ = ["Waterfall"]
|
|
|
47
48
|
|
|
48
49
|
WATERFALL_CONFIG: dict[str, Any] = PLOT_CONFIG.get("waterfall", {})
|
|
49
50
|
|
|
51
|
+
# points of air between two value labels for them not to read as one
|
|
52
|
+
_LABEL_GAP_PT = 2.0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _drop_crowded(
|
|
56
|
+
fig: Figure, labels: list[Annotation], is_total: list[bool]
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Remove the deltas' value labels if any two neighbours collide.
|
|
59
|
+
|
|
60
|
+
Measured on the drawn figure: all of them go or none, so the
|
|
61
|
+
chart never labels some steps and not others; the totals stay.
|
|
62
|
+
"""
|
|
63
|
+
if len(labels) < 2:
|
|
64
|
+
return
|
|
65
|
+
fig.draw_without_rendering()
|
|
66
|
+
gap = _LABEL_GAP_PT * fig.dpi / 72
|
|
67
|
+
boxes = [label.get_window_extent() for label in labels]
|
|
68
|
+
if any(
|
|
69
|
+
a.x1 + gap > b.x0 and a.y0 < b.y1 and b.y0 < a.y1
|
|
70
|
+
for a, b in zip(boxes, boxes[1:])
|
|
71
|
+
):
|
|
72
|
+
for label, total in zip(labels, is_total):
|
|
73
|
+
if not total:
|
|
74
|
+
label.remove()
|
|
75
|
+
|
|
50
76
|
|
|
51
77
|
class Waterfall(FigureLifecycle):
|
|
52
78
|
"""Waterfall / bridge chart with the tesorotools visual style.
|
|
@@ -205,12 +231,12 @@ class Waterfall(FigureLifecycle):
|
|
|
205
231
|
inc_color = WATERFALL_CONFIG.get("increase_color", "C0")
|
|
206
232
|
dec_color = WATERFALL_CONFIG.get("decrease_color", "C9")
|
|
207
233
|
light_alpha = float(WATERFALL_CONFIG.get("light_alpha", 0.35))
|
|
234
|
+
# the colour follows what the bar is, not where it sits: a first or
|
|
235
|
+
# last bar left out of ``totals`` is a delta like the rest
|
|
208
236
|
colors: list[Any] = []
|
|
209
237
|
for i in range(n):
|
|
210
|
-
if i
|
|
211
|
-
colors.append(to_rgba(start_color))
|
|
212
|
-
elif i == n - 1 or is_total[i]:
|
|
213
|
-
colors.append(to_rgba(end_color))
|
|
238
|
+
if is_total[i]:
|
|
239
|
+
colors.append(to_rgba(start_color if i == 0 else end_color))
|
|
214
240
|
else:
|
|
215
241
|
base = inc_color if values[i] >= 0 else dec_color
|
|
216
242
|
colors.append(to_rgba(base, light_alpha))
|
|
@@ -243,19 +269,11 @@ class Waterfall(FigureLifecycle):
|
|
|
243
269
|
zorder=0,
|
|
244
270
|
)
|
|
245
271
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
ax,
|
|
252
|
-
x,
|
|
253
|
-
tops,
|
|
254
|
-
values,
|
|
255
|
-
is_total,
|
|
256
|
-
font_pt,
|
|
257
|
-
only_totals=self.compact,
|
|
258
|
-
)
|
|
272
|
+
labels = (
|
|
273
|
+
self._annotate_bars(ax, x, tops, values, is_total, font_pt)
|
|
274
|
+
if self.annotate
|
|
275
|
+
else []
|
|
276
|
+
)
|
|
259
277
|
|
|
260
278
|
style_spines(
|
|
261
279
|
ax,
|
|
@@ -286,6 +304,11 @@ class Waterfall(FigureLifecycle):
|
|
|
286
304
|
top = float(tops.max())
|
|
287
305
|
ax.set_ylim(bottom, top + (top - bottom) * headroom)
|
|
288
306
|
|
|
307
|
+
if self.compact:
|
|
308
|
+
# a compact chart at 7.4 cm has no room for every label; one as
|
|
309
|
+
# wide as a slide does, so the deltas go only if they collide
|
|
310
|
+
_drop_crowded(fig, labels, is_total)
|
|
311
|
+
|
|
289
312
|
if self.plot_size is not None and not self.compact:
|
|
290
313
|
adjust_figure_for_plot_size(fig, ax, self.plot_size)
|
|
291
314
|
|
|
@@ -299,22 +322,14 @@ class Waterfall(FigureLifecycle):
|
|
|
299
322
|
values: np.ndarray[tuple[int], np.dtype[np.float64]],
|
|
300
323
|
is_total: list[bool],
|
|
301
324
|
font_pt: float | None,
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
"""Write each bar's value just above its top (totals in bold).
|
|
306
|
-
|
|
307
|
-
With ``only_totals`` set, the floating deltas are left
|
|
308
|
-
unlabelled -- used by compact charts where the per-delta
|
|
309
|
-
labels would overlap.
|
|
310
|
-
"""
|
|
325
|
+
) -> list[Annotation]:
|
|
326
|
+
"""Write each bar's value just above its top (totals in bold)."""
|
|
327
|
+
labels: list[Annotation] = []
|
|
311
328
|
for i in range(len(x)):
|
|
312
|
-
if only_totals and not is_total[i]:
|
|
313
|
-
continue
|
|
314
329
|
text = format_annotation(
|
|
315
330
|
float(values[i]), self.fmt.decimals, self.fmt.units
|
|
316
331
|
)
|
|
317
|
-
ax.annotate( # type: ignore[reportUnknownMemberType]
|
|
332
|
+
label = ax.annotate( # type: ignore[reportUnknownMemberType]
|
|
318
333
|
text,
|
|
319
334
|
xy=(float(x[i]), float(tops[i])),
|
|
320
335
|
xytext=(0, 3),
|
|
@@ -324,6 +339,8 @@ class Waterfall(FigureLifecycle):
|
|
|
324
339
|
fontsize=font_pt,
|
|
325
340
|
fontweight="bold" if is_total[i] else "normal",
|
|
326
341
|
)
|
|
342
|
+
labels.append(label)
|
|
343
|
+
return labels
|
|
327
344
|
|
|
328
345
|
def save(
|
|
329
346
|
self,
|
|
@@ -86,6 +86,8 @@ FOREIGN_WORDS: tuple[str, ...] = (
|
|
|
86
86
|
"default",
|
|
87
87
|
"tapering",
|
|
88
88
|
"carry trade",
|
|
89
|
+
"home bias",
|
|
90
|
+
"unit-linked",
|
|
89
91
|
"benchmark",
|
|
90
92
|
"trading",
|
|
91
93
|
"outlook",
|
|
@@ -108,12 +110,14 @@ _WORD_BEFORE = re.compile(r"(\w+)\s+$")
|
|
|
108
110
|
_BEFORE_NUMBER = re.compile(
|
|
109
111
|
r"(?:gráfico|tabla|apartado|sección|capítulo|anexo|nivel|punto|fase"
|
|
110
112
|
r"|pilar|artículo|nota|paso|grupo|tramo|versión|n\.º|nº"
|
|
113
|
+
r"|alternativa|opción|escenario|propuesta|etapa|hito|eje|objetivo"
|
|
114
|
+
r"|medida|caso|tema"
|
|
111
115
|
r"|art\.|arts\.|núm\.|pág\.|págs\.|p\.|pp\.|cap\.|apdo\.|vol\.)\s*$",
|
|
112
116
|
re.I,
|
|
113
117
|
)
|
|
114
118
|
_UNIT_AFTER = re.compile(
|
|
115
|
-
r"\s*(?:%|por\s+
|
|
116
|
-
r"|billones?\b|billions?\b"
|
|
119
|
+
r"\s*(?:%|por\s+cien(?:to)?\b|pb\b|pp\b|puntos?\b|millones?\b|mil\b"
|
|
120
|
+
r"|miles\b|billones?\b|billions?\b|bn\b|tn\b"
|
|
117
121
|
r"|euros?\b|dólares?\b|€|\$|°|veces\b|x\b|h\b|horas?\b|de\s+(?:"
|
|
118
122
|
+ _MONTHS.lower()
|
|
119
123
|
+ r")\b)",
|
|
@@ -145,10 +149,10 @@ def _real_decimal(text: str, match: re.Match[str]) -> bool:
|
|
|
145
149
|
def _month_context(text: str, match: re.Match[str]) -> bool:
|
|
146
150
|
"""«de Mayo» outside a proper name such as «Plaza de Mayo».
|
|
147
151
|
|
|
148
|
-
Only «de» can join a proper name; «en Octubre»
|
|
149
|
-
also after a capitalised word or in a title.
|
|
152
|
+
Only «de» can join a proper name; «en Octubre» or «desde Octubre» is
|
|
153
|
+
always a fault, also after a capitalised word or in a title.
|
|
150
154
|
"""
|
|
151
|
-
if
|
|
155
|
+
if match.group(0).split()[0].lower() != "de":
|
|
152
156
|
return True
|
|
153
157
|
word = _WORD_BEFORE.search(text[: match.start()])
|
|
154
158
|
return word is None or not word.group(1)[0].isupper()
|
|
@@ -187,6 +191,8 @@ def _small_number(text: str, match: re.Match[str]) -> bool:
|
|
|
187
191
|
before, after = text[: match.start()], text[match.end() :]
|
|
188
192
|
if _BEFORE_NUMBER.search(before) or _UNIT_AFTER.match(after):
|
|
189
193
|
return False
|
|
194
|
+
if _quoted(text, match.start()): # a name or a quote: «peso: 2»
|
|
195
|
+
return False
|
|
190
196
|
if _NAME_AFTER.match(after) or _RANGE_AFTER.match(after):
|
|
191
197
|
return False
|
|
192
198
|
if _RANGE_BEFORE.search(before):
|
|
@@ -195,8 +201,14 @@ def _small_number(text: str, match: re.Match[str]) -> bool:
|
|
|
195
201
|
|
|
196
202
|
|
|
197
203
|
def _empty_adjective(text: str, match: re.Match[str]) -> bool:
|
|
198
|
-
|
|
204
|
+
word = match.group(0).lower()
|
|
205
|
+
if word.startswith("hist"):
|
|
199
206
|
return not _HISTORIC.search(text[: match.start()])
|
|
207
|
+
if word.startswith("significativ"):
|
|
208
|
+
# the statistical term, not an intensifier
|
|
209
|
+
return not re.search(
|
|
210
|
+
r"estadísticamente\s+$", text[: match.start()], re.I
|
|
211
|
+
)
|
|
200
212
|
return True
|
|
201
213
|
|
|
202
214
|
|
|
@@ -283,7 +295,7 @@ _RULES: tuple[_Rule, ...] = (
|
|
|
283
295
|
"todo",
|
|
284
296
|
),
|
|
285
297
|
_r(
|
|
286
|
-
r"\bpor
|
|
298
|
+
r"\bpor cien(?:to)?\b(?!\s+(?:de|del)\b)",
|
|
287
299
|
"porcentaje",
|
|
288
300
|
"escribe «%» con espacio fijo",
|
|
289
301
|
scope="prosa",
|
|
@@ -346,7 +358,8 @@ _RULES: tuple[_Rule, ...] = (
|
|
|
346
358
|
flags=re.I,
|
|
347
359
|
),
|
|
348
360
|
_r(
|
|
349
|
-
rf"\b(?:de|en|el|del|\d+)
|
|
361
|
+
rf"\b(?:de|en|el|del|desde|hasta|entre|hacia|para|y|\d+)"
|
|
362
|
+
rf" (?:{_MONTHS})\b",
|
|
350
363
|
"mes",
|
|
351
364
|
"los meses y los días de la semana van en minúscula",
|
|
352
365
|
_month_context,
|
|
@@ -391,6 +404,27 @@ _RULES: tuple[_Rule, ...] = (
|
|
|
391
404
|
scope="prosa",
|
|
392
405
|
flags=re.I,
|
|
393
406
|
),
|
|
407
|
+
_r(
|
|
408
|
+
r"\bse\s+desplom\w*|\bdesplom(?:e|es|ó|aron)\b",
|
|
409
|
+
"registro",
|
|
410
|
+
"registro coloquial: «cayó un 5 %», «registró la mayor caída desde...»",
|
|
411
|
+
scope="prosa",
|
|
412
|
+
flags=re.I,
|
|
413
|
+
),
|
|
414
|
+
_r(
|
|
415
|
+
r"\bse\s+dispar(?:a|an|ó|aron|aba|aban)\b",
|
|
416
|
+
"registro",
|
|
417
|
+
"registro coloquial: «subió con fuerza», «aumentó un 5 %»",
|
|
418
|
+
scope="prosa",
|
|
419
|
+
flags=re.I,
|
|
420
|
+
),
|
|
421
|
+
_r(
|
|
422
|
+
r"\btir[oó]n(?:es)?\b",
|
|
423
|
+
"registro",
|
|
424
|
+
"registro coloquial: «aumento», «repunte»",
|
|
425
|
+
scope="prosa",
|
|
426
|
+
flags=re.I,
|
|
427
|
+
),
|
|
394
428
|
_r(
|
|
395
429
|
r"\ba trozos\b",
|
|
396
430
|
"registro",
|
|
@@ -406,7 +440,8 @@ _RULES: tuple[_Rule, ...] = (
|
|
|
406
440
|
flags=re.I,
|
|
407
441
|
),
|
|
408
442
|
_r(
|
|
409
|
-
r"\b(?:burbuja|sobrevalora\w*|excesiv[oa]s?|insostenibles?
|
|
443
|
+
r"\b(?:burbuja|sobrevalora\w*|excesiv[oa]s?|insostenibles?"
|
|
444
|
+
r"|preocupantes?|alarmantes?)\b",
|
|
410
445
|
"juicio",
|
|
411
446
|
"sin juicios subjetivos: descríbelo con indicadores («valoraciones"
|
|
412
447
|
" elevadas en perspectiva histórica») o atribúyelo a quien lo dice",
|
|
@@ -423,7 +458,8 @@ _RULES: tuple[_Rule, ...] = (
|
|
|
423
458
|
),
|
|
424
459
|
_r(
|
|
425
460
|
r"\b(?:enorme|enormes|histórico|histórica|históricos|históricas|muy"
|
|
426
|
-
r"|extraordinari[oa]s?|espectacular(?:es)?|dramátic[oa]s
|
|
461
|
+
r"|extraordinari[oa]s?|espectacular(?:es)?|dramátic[oa]s?|realmente"
|
|
462
|
+
r"|claramente|significativ[oa]s?|significativamente|notablemente)\b",
|
|
427
463
|
"precision",
|
|
428
464
|
"adjetivo o intensificador que no añade información: úsalo solo si"
|
|
429
465
|
" una cifra lo respalda",
|
|
@@ -439,7 +475,8 @@ _RULES: tuple[_Rule, ...] = (
|
|
|
439
475
|
flags=re.I,
|
|
440
476
|
),
|
|
441
477
|
_r(
|
|
442
|
-
r"\b(?:
|
|
478
|
+
r"\b(?:[Ee]l|[Ll]a|[Ll]os|[Ll]as|[Dd]el)\s+(?:Presidente|Presidenta"
|
|
479
|
+
r"|Ministro|Ministra"
|
|
443
480
|
r"|Gobernador|Gobernadora|Vicepresidente|Vicepresidenta|Consejero"
|
|
444
481
|
r"|Consejera|Bolsa|Euro|Dólar)\b",
|
|
445
482
|
"mayusculas",
|
|
@@ -547,7 +584,11 @@ _KNOWN_ACRONYMS = {
|
|
|
547
584
|
"CNH",
|
|
548
585
|
"RMB",
|
|
549
586
|
}
|
|
550
|
-
|
|
587
|
+
#: An acronym: in capitals («CNMV») or with a lowercase letter inside
|
|
588
|
+
#: («AIReF», «BdE»).
|
|
589
|
+
_ACRONYM = re.compile(
|
|
590
|
+
r"\b(?:[A-ZÁÉÍÓÚÑ]{2,6}|[A-Z]{2,4}[a-z][A-Z]{1,2}|[A-Z][a-z][A-Z])\b"
|
|
591
|
+
)
|
|
551
592
|
|
|
552
593
|
|
|
553
594
|
def _scope_texts(text: str, scope: str) -> Iterator[_Rule]:
|
|
@@ -386,12 +386,55 @@ def _hash_comments(text: str, line: int) -> None:
|
|
|
386
386
|
)
|
|
387
387
|
|
|
388
388
|
|
|
389
|
+
#: A decimal comma («3,5», «1.234,5»), not a date («2024-01-01,2024»).
|
|
390
|
+
_DECIMAL_COMMA = re.compile(
|
|
391
|
+
r"(?<![\d\-])(?:\d{1,3}(?:\.\d{3})+|\d+),\d+(?![\d\-:])"
|
|
392
|
+
)
|
|
393
|
+
#: A bracket, or a comma that separates the elements of a flow list.
|
|
394
|
+
_SEPARATOR = re.compile(r"[\[\]{}]|,(?!\d)|(?<!\d),")
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _decimal_commas(text: str, line: int) -> None:
|
|
398
|
+
"""Reject «3,5» inside a flow list or map (``[...]``, ``{...}``):
|
|
399
|
+
YAML splits it at the comma, into «3» and «5»."""
|
|
400
|
+
depth = 0
|
|
401
|
+
for offset, raw in enumerate(text.splitlines()):
|
|
402
|
+
# quoted text keeps its length, so positions match *raw*
|
|
403
|
+
bare = _QUOTED.sub(lambda m: "'" + "_" * (len(m[0]) - 2) + "'", raw)
|
|
404
|
+
marks = [(m.start(), m[0]) for m in _SEPARATOR.finditer(bare)]
|
|
405
|
+
for match in _DECIMAL_COMMA.finditer(bare):
|
|
406
|
+
before = [(n, c) for n, c in marks if n < match.start()]
|
|
407
|
+
opened = depth + sum(
|
|
408
|
+
1 if c in "[{" else -1 for _, c in before if c != ","
|
|
409
|
+
)
|
|
410
|
+
if opened <= 0:
|
|
411
|
+
continue
|
|
412
|
+
start = before[-1][0] + 1 if before else 0
|
|
413
|
+
end = next((n for n, _ in marks if n >= match.end()), len(raw))
|
|
414
|
+
element = re.sub(r"^[\w-]+:\s+", "", raw[start:end].strip())
|
|
415
|
+
if re.fullmatch(r"[-+]?[\d.]+(?:,\d+)+", element):
|
|
416
|
+
plain = element.replace(".", "").replace(",", ".")
|
|
417
|
+
hint = (
|
|
418
|
+
f"los decimales van con punto ({plain}); si son varias"
|
|
419
|
+
" cifras, sepáralas con coma y espacio"
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
hint = f'entrecomíllalo ("{element}")'
|
|
423
|
+
raise NoteSyntaxError(
|
|
424
|
+
line + offset,
|
|
425
|
+
f"«{element}» lleva coma decimal y, entre [...] o {{...}},"
|
|
426
|
+
f" YAML lo parte en dos por la coma: {hint}",
|
|
427
|
+
)
|
|
428
|
+
depth += sum(1 if c in "[{" else -1 for _, c in marks if c != ",")
|
|
429
|
+
|
|
430
|
+
|
|
389
431
|
def _yaml(text: str, line: int) -> Any:
|
|
390
432
|
"""``yaml.safe_load`` with the errors in Spanish and with their line.
|
|
391
433
|
|
|
392
434
|
*line* is the line of the source where *text* starts.
|
|
393
435
|
"""
|
|
394
436
|
_hash_comments(text, line)
|
|
437
|
+
_decimal_commas(text, line)
|
|
395
438
|
try:
|
|
396
439
|
return yaml.safe_load(_windows_paths(text))
|
|
397
440
|
except yaml.YAMLError as exc:
|
|
@@ -31,6 +31,8 @@ BLUE_PALE = "AEBAE7"
|
|
|
31
31
|
BLUE_FAINT = "ECF0FD"
|
|
32
32
|
#: Secondary text: captions, metadata, stale rows.
|
|
33
33
|
GREY = "6D6D6D"
|
|
34
|
+
#: Plot area of the slide charts, under white grid lines.
|
|
35
|
+
GREY_PANEL = "EFEFEF"
|
|
34
36
|
#: Tesoro yellow: highlights of the presentations (bands, frames, the
|
|
35
37
|
#: section subtitles of the index) and its light shade for fills.
|
|
36
38
|
YELLOW = "FFBD4C"
|