From fd9ed559ffcd14f86afcf23ce0f141af8d5eac6d Mon Sep 17 00:00:00 2001 From: DOKOS-TAYOS Date: Mon, 24 Aug 2026 17:35:15 +0200 Subject: [PATCH] release: RegressionLab 1.1.4 --- CHANGELOG.md | 7 ++ CITATION.cff | 2 +- README.md | 4 +- docs/README.md | 2 +- docs/api/config.md | 2 +- docs/index.md | 2 +- pyproject.toml | 2 +- .../locale/es/LC_MESSAGES/api/index.po | 2 +- .../locale/es/LC_MESSAGES/configuration.po | 2 +- .../locale/es/LC_MESSAGES/contributing.po | 2 +- .../locale/es/LC_MESSAGES/customization.po | 2 +- .../locale/es/LC_MESSAGES/extending.po | 2 +- sphinx-docs/locale/es/LC_MESSAGES/index.po | 2 +- .../locale/es/LC_MESSAGES/installation.po | 2 +- .../locale/es/LC_MESSAGES/introduction.po | 2 +- sphinx-docs/locale/es/LC_MESSAGES/license.po | 2 +- .../locale/es/LC_MESSAGES/modules/core.po | 2 +- .../es/LC_MESSAGES/modules/data_analysis.po | 2 +- .../locale/es/LC_MESSAGES/modules/fitting.po | 2 +- .../locale/es/LC_MESSAGES/modules/frontend.po | 2 +- .../locale/es/LC_MESSAGES/modules/loaders.po | 2 +- .../locale/es/LC_MESSAGES/modules/plotting.po | 2 +- .../es/LC_MESSAGES/modules/streamlit_app.po | 2 +- .../locale/es/LC_MESSAGES/modules/utils.po | 2 +- .../locale/es/LC_MESSAGES/streamlit-guide.po | 2 +- .../locale/es/LC_MESSAGES/tkinter-guide.po | 2 +- .../locale/es/LC_MESSAGES/troubleshooting.po | 2 +- sphinx-docs/locale/es/LC_MESSAGES/usage.po | 2 +- sphinx-docs/source/conf.py | 4 +- sphinx-docs/source/index.rst | 2 +- src/config/constants.py | 2 +- src/plotting/plot_utils.py | 113 ++++++++++++++++-- tests/test_plot_utils.py | 111 +++++++++++++++++ 33 files changed, 252 insertions(+), 43 deletions(-) create mode 100644 tests/test_plot_utils.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 83e1dc6..4a435b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.4] - 2026-08-24 + +### Fixed + +- **Scientific plot labels**: Pair plots and fitted plots preserve MathText labels and titles, including uncertainty headers such as `u$x (m)$`. If Matplotlib cannot parse a label or title, the affected plot automatically retries with readable plain text instead of failing. + ## [1.1.3] - 2026-05-17 ### Added @@ -237,6 +243,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Initial 0.8.x release. See repository history and documentation for features and changes prior to 0.8.1. [1.1.3]: https://github.com/DOKOS-TAYOS/RegressionLab/compare/v1.1.2...v1.1.3 +[1.1.4]: https://github.com/DOKOS-TAYOS/RegressionLab/compare/v1.1.3...v1.1.4 [1.1.2]: https://github.com/DOKOS-TAYOS/RegressionLab/compare/v1.1.1...v1.1.2 [1.1.1]: https://github.com/DOKOS-TAYOS/RegressionLab/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/DOKOS-TAYOS/RegressionLab/compare/v1.0.0...v1.1.0 diff --git a/CITATION.cff b/CITATION.cff index ff9b16a..8a321d9 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -7,7 +7,7 @@ authors: given-names: Alejandro email: alejandro.mata.ali@gmail.com license: MIT -version: 1.1.3 +version: 1.1.4 repository-code: https://github.com/DOKOS-TAYOS/RegressionLab url: https://github.com/DOKOS-TAYOS/RegressionLab abstract: >- diff --git a/README.md b/README.md index 74cfede..ecc5e47 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Python](https://img.shields.io/badge/Python-3.12+-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/) [![License](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE) -[![Version](https://img.shields.io/badge/version-1.1.3-blue.svg?style=for-the-badge)](https://github.com/DOKOS-TAYOS/RegressionLab) +[![Version](https://img.shields.io/badge/version-1.1.4-blue.svg?style=for-the-badge)](https://github.com/DOKOS-TAYOS/RegressionLab) [![Streamlit](https://img.shields.io/badge/Streamlit-FF4B4B?style=for-the-badge&logo=streamlit&logoColor=white)](https://regressionlab.streamlit.app/) [![SciPy](https://img.shields.io/badge/SciPy-8CAAE6?style=for-the-badge&logo=scipy&logoColor=white)](https://scipy.org/) [![NumPy](https://img.shields.io/badge/NumPy-013243?style=for-the-badge&logo=numpy&logoColor=white)](https://numpy.org/) @@ -212,7 +212,7 @@ For information about third-party libraries and their licenses, see
-**Version**: 1.1.3 • **Last Updated**: May 2026 +**Version**: 1.1.4 • **Last Updated**: August 2026 Made with ❤️ by [Alejandro Mata Ali](https://github.com/DOKOS-TAYOS) diff --git a/docs/README.md b/docs/README.md index 4fa529c..b3ae5bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -265,6 +265,6 @@ Documentation should be reviewed and updated: --- -**Documentation Version**: 1.1.3 +**Documentation Version**: 1.1.4 **Last Updated**: February 2026 **Maintainer**: Alejandro Mata Ali diff --git a/docs/api/config.md b/docs/api/config.md index f88967f..8ab6151 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -21,7 +21,7 @@ Usage remains the same: import from `config` (e.g. `from config import PLOT_CONF ### Application Metadata ```python -__version__ = "1.1.3" +__version__ = "1.1.4" __author__ = "Alejandro Mata Ali" __email__ = "alejandro.mata.ali@gmail.com" ``` diff --git a/docs/index.md b/docs/index.md index adb9897..c016a79 100644 --- a/docs/index.md +++ b/docs/index.md @@ -103,6 +103,6 @@ If you need assistance: --- -**Version**: 1.1.3 +**Version**: 1.1.4 **Author**: Alejandro Mata Ali **License**: MIT diff --git a/pyproject.toml b/pyproject.toml index 54fc789..66462e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "regressionlab" -version = "1.1.3" +version = "1.1.4" description = "Scientific curve fitting application with GUI" readme = "README.md" requires-python = ">=3.12" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/api/index.po b/sphinx-docs/locale/es/LC_MESSAGES/api/index.po index 0cbadd9..ff29324 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/api/index.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/api/index.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/configuration.po b/sphinx-docs/locale/es/LC_MESSAGES/configuration.po index 515c75c..b04b17c 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/configuration.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/configuration.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/contributing.po b/sphinx-docs/locale/es/LC_MESSAGES/contributing.po index 229904a..c11f919 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/contributing.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/contributing.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/customization.po b/sphinx-docs/locale/es/LC_MESSAGES/customization.po index ac2b789..c49ceff 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/customization.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/customization.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/extending.po b/sphinx-docs/locale/es/LC_MESSAGES/extending.po index 17b1ebb..4df0973 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/extending.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/extending.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/index.po b/sphinx-docs/locale/es/LC_MESSAGES/index.po index 1fcadca..e5a178e 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/index.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/index.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/installation.po b/sphinx-docs/locale/es/LC_MESSAGES/installation.po index f407793..322e055 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/installation.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/installation.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/introduction.po b/sphinx-docs/locale/es/LC_MESSAGES/introduction.po index 4fff2cd..579b015 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/introduction.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/introduction.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/license.po b/sphinx-docs/locale/es/LC_MESSAGES/license.po index 7711266..55f17da 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/license.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/license.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/core.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/core.po index 887b9a0..3e94ce9 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/core.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/core.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/data_analysis.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/data_analysis.po index c290477..52bfb92 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/data_analysis.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/data_analysis.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/fitting.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/fitting.po index 909c9f1..4ff6b0f 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/fitting.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/fitting.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/frontend.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/frontend.po index 9d22cfe..cbdfc32 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/frontend.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/frontend.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/loaders.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/loaders.po index 45ca032..2a905bb 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/loaders.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/loaders.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/plotting.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/plotting.po index f8cc603..d575a3a 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/plotting.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/plotting.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/streamlit_app.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/streamlit_app.po index 98dbfe4..77c7c63 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/streamlit_app.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/streamlit_app.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/modules/utils.po b/sphinx-docs/locale/es/LC_MESSAGES/modules/utils.po index 797cdf9..bf519f5 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/modules/utils.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/modules/utils.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/streamlit-guide.po b/sphinx-docs/locale/es/LC_MESSAGES/streamlit-guide.po index ee1bc10..4e38e6a 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/streamlit-guide.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/streamlit-guide.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/tkinter-guide.po b/sphinx-docs/locale/es/LC_MESSAGES/tkinter-guide.po index 4bd98e7..026d51b 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/tkinter-guide.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/tkinter-guide.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/troubleshooting.po b/sphinx-docs/locale/es/LC_MESSAGES/troubleshooting.po index bebf17b..a11bc59 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/troubleshooting.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/troubleshooting.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-13 19:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/locale/es/LC_MESSAGES/usage.po b/sphinx-docs/locale/es/LC_MESSAGES/usage.po index 6cc50ee..e42aff2 100644 --- a/sphinx-docs/locale/es/LC_MESSAGES/usage.po +++ b/sphinx-docs/locale/es/LC_MESSAGES/usage.po @@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: RegressionLab 1.1.3\n" +"Project-Id-Version: RegressionLab 1.1.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-14 18:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/sphinx-docs/source/conf.py b/sphinx-docs/source/conf.py index ffd22ae..966122d 100644 --- a/sphinx-docs/source/conf.py +++ b/sphinx-docs/source/conf.py @@ -587,8 +587,8 @@ def add_filters(_app): except ImportError: pass # dotenv not required for documentation build -release = "1.1.3" -version = "1.1.3" +release = "1.1.4" +version = "1.1.4" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/sphinx-docs/source/index.rst b/sphinx-docs/source/index.rst index 5fee0c9..3688d68 100644 --- a/sphinx-docs/source/index.rst +++ b/sphinx-docs/source/index.rst @@ -14,7 +14,7 @@

Python License - Version + Version Streamlit SciPy NumPy diff --git a/src/config/constants.py b/src/config/constants.py index e6ca3b4..000d1ab 100644 --- a/src/config/constants.py +++ b/src/config/constants.py @@ -7,7 +7,7 @@ from config.equation_registry import get_equation_registry # Application version number -__version__ = "1.1.3" +__version__ = "1.1.4" # Application metadata __author__ = "Alejandro Mata Ali" diff --git a/src/plotting/plot_utils.py b/src/plotting/plot_utils.py index 187b1f8..fb77860 100644 --- a/src/plotting/plot_utils.py +++ b/src/plotting/plot_utils.py @@ -7,6 +7,7 @@ # Standard library from pathlib import Path +import re from typing import Any, Dict, List, Optional, Sequence, Tuple, Union # Third-party packages @@ -43,6 +44,80 @@ _PAIR_PLOT_CELL_INCHES_MIN = 1.4 # Minimum per cell so many variables stay readable +def _format_plot_label(name: str) -> str: + """Return a MathText-safe label while preserving valid scientific notation.""" + math_prefix = "" + if name.startswith("u$") and name.endswith("$") and name.count("$") == 2: + math_prefix = r"\mathrm{u}" + content = name[2:-1] + elif name.startswith("$") and name.endswith("$") and name.count("$") == 2: + content = name[1:-1] + else: + return name + + match = re.fullmatch(r"(?P[^()]+?)\s*\((?P[^()]*)\)", content) + if match is None: + return f"${math_prefix}{content}$" + + symbol = match.group("symbol").strip() + unit = match.group("unit").strip() + return rf"${math_prefix}{symbol}\;(\mathrm{{{unit}}})$" + + +def _plain_plot_label(name: str) -> str: + """Return a readable label after MathText parsing has failed.""" + return name.replace("$", "").replace("\\", "") + + +def _apply_plain_pair_plot_labels(axes: Any, names: List[str]) -> None: + """Replace pair-plot labels with text that does not require MathText.""" + for i, row in enumerate(axes): + for j, ax in enumerate(row): + if i == j: + for text in ax.texts: + text.set_text(_plain_plot_label(names[i])) + else: + ax.set_xlabel(_plain_plot_label(names[j])) + ax.set_ylabel(_plain_plot_label(names[i])) + + +def _tight_layout_pair_plot(fig: Any, axes: Any, names: List[str]) -> None: + """Apply pair-plot layout, falling back to plain labels on MathText errors.""" + try: + fig.tight_layout(pad=1.2, h_pad=1.0, w_pad=1.0) + except ValueError as error: + if "Parse" not in str(error): + raise + logger.warning("MathText failed in pair plot; retrying with plain labels.") + _apply_plain_pair_plot_labels(axes, names) + fig.tight_layout(pad=1.2, h_pad=1.0, w_pad=1.0) + + +def _tight_layout_fit_plot( + fig: Any, + ax: Any, + x_name: str, + y_name: str, + fit_name: str, + *, + show_title: bool, + title_font: Any, + axis_font: Any, +) -> None: + """Apply fit-plot layout, falling back to plain text on MathText errors.""" + try: + fig.tight_layout() + except ValueError as error: + if "Parse" not in str(error): + raise + logger.warning("MathText failed in fit plot; retrying with plain labels.") + ax.set_xlabel(_plain_plot_label(x_name), fontproperties=axis_font) + ax.set_ylabel(_plain_plot_label(y_name), fontproperties=axis_font) + if show_title: + ax.set_title(_plain_plot_label(fit_name), fontproperties=title_font) + fig.tight_layout() + + def _save_figure( fig: Any, save_path: Union[str, Path], @@ -141,10 +216,11 @@ def create_pair_plots( edgecolors=_PAIR_PLOT_SCATTER_EDGE, linewidths=0.5, ) - ax.set_xlabel(names[0], fontsize=10) - ax.set_ylabel(names[0], fontsize=10) + label = _format_plot_label(names[0]) + ax.set_xlabel(label, fontsize=10) + ax.set_ylabel(label, fontsize=10) ax.tick_params(axis="both", labelsize=9) - plt.tight_layout() + fig.tight_layout() if output_path: return _save_figure(fig, output_path, plot_config, log_saved=True) return fig @@ -164,7 +240,7 @@ def create_pair_plots( ax.text( 0.5, 0.5, - names[i], + _format_plot_label(names[i]), ha="center", va="center", fontsize=min(10, max(8, 14 - n)), @@ -185,11 +261,17 @@ def create_pair_plots( linewidths=0.4, ) ax.grid(True, linestyle="--", alpha=0.35) - ax.set_xlabel(xcol, fontsize=min(9, max(7, 11 - n // 2))) - ax.set_ylabel(ycol, fontsize=min(9, max(7, 11 - n // 2))) + ax.set_xlabel( + _format_plot_label(xcol), + fontsize=min(9, max(7, 11 - n // 2)), + ) + ax.set_ylabel( + _format_plot_label(ycol), + fontsize=min(9, max(7, 11 - n // 2)), + ) ax.tick_params(axis="both", labelsize=8) ax.set_aspect("auto") - plt.tight_layout(pad=1.2, h_pad=1.0, w_pad=1.0) + _tight_layout_pair_plot(fig, axes, names) if output_path: return _save_figure(fig, output_path, plot_config, log_saved=True) @@ -325,17 +407,26 @@ def create_plot( logger.debug("Data plotted") - ax.set_xlabel(x_name, fontproperties=fonta) - ax.set_ylabel(y_name, fontproperties=fonta) + ax.set_xlabel(_format_plot_label(x_name), fontproperties=fonta) + ax.set_ylabel(_format_plot_label(y_name), fontproperties=fonta) if plot_config.get("show_title", False): - ax.set_title(fit_name, fontproperties=fontt) + ax.set_title(_format_plot_label(fit_name), fontproperties=fontt) if plot_config.get("show_grid", False): ax.grid(True, alpha=0.3) ax.tick_params(axis="both", which="major", labelsize=font_config["tick_size"]) - plt.tight_layout() + _tight_layout_fit_plot( + fig, + ax, + x_name, + y_name, + fit_name, + show_title=plot_config.get("show_title", False), + title_font=fontt, + axis_font=fonta, + ) logger.debug("Saving plot to: %s", save_path) return _save_figure(fig, save_path, plot_config, log_saved=True) diff --git a/tests/test_plot_utils.py b/tests/test_plot_utils.py new file mode 100644 index 0000000..db8de65 --- /dev/null +++ b/tests/test_plot_utils.py @@ -0,0 +1,111 @@ +"""Tests for plotting utilities.""" + +from pathlib import Path + +import matplotlib +import numpy as np +import pandas as pd + +matplotlib.use("Agg") + +import matplotlib.pyplot as plt + +from config import PLOT_CONFIG +from loaders import excel_reader, get_variable_names +from plotting import create_pair_plots, create_plot + + +def test_pair_plots_preserve_dollar_delimited_column_names_as_mathtext() -> None: + """Pair plots must retain scientific MathText column labels.""" + data = pd.DataFrame( + { + "$x (m)$": [0.0, 1.0, 2.0], + "$y (m)$": [1.0, 2.0, 3.0], + } + ) + + figure = create_pair_plots(data, ["$x (m)$", "$y (m)$"]) + figure.canvas.draw() + + assert figure.axes[1].get_xlabel() == r"$y\;(\mathrm{m})$" + assert figure.axes[1].get_ylabel() == r"$x\;(\mathrm{m})$" + + plt.close(figure) + + +def test_pair_plots_format_uncertainty_mathtext_as_one_expression() -> None: + """Uncertainty prefixes must be included in the MathText expression.""" + data = pd.DataFrame( + { + "$x (m)$": [0.0, 1.0, 2.0], + "u$x (m)$": [0.1, 0.1, 0.1], + } + ) + + figure = create_pair_plots(data, ["$x (m)$", "u$x (m)$"]) + figure.canvas.draw() + + assert figure.axes[1].get_xlabel() == r"$\mathrm{u}x\;(\mathrm{m})$" + + plt.close(figure) + + +def test_pair_plots_fall_back_to_plain_labels_when_mathtext_is_invalid() -> None: + """Invalid MathText labels must not prevent a pair plot from rendering.""" + data = pd.DataFrame( + { + r"$x\notacommand$": [0.0, 1.0, 2.0], + "$y (m)$": [1.0, 2.0, 3.0], + } + ) + + figure = create_pair_plots(data, [r"$x\notacommand$", "$y (m)$"]) + figure.canvas.draw() + + assert figure.axes[1].get_ylabel() == "xnotacommand" + + plt.close(figure) + + +def test_pair_plots_render_mathtext_headers_from_all_input_workbooks() -> None: + """Every bundled workbook with MathText headers must render without errors.""" + input_dir = Path(__file__).parent.parent / "input" + + for workbook in sorted(input_dir.glob("*.xlsx")): + data = excel_reader(str(workbook)) + variable_names = get_variable_names(data, filter_uncertainty=True) + figure = create_pair_plots(data, variable_names) + figure.canvas.draw() + + if len(variable_names) > 1: + for label in (figure.axes[1].get_xlabel(), figure.axes[1].get_ylabel()): + assert ( + label.startswith("$") and label.endswith("$") + if "$" in label + else True + ) + + plt.close(figure) + + +def test_fit_plot_falls_back_to_plain_title_when_mathtext_is_invalid( + tmp_path: Path, +) -> None: + """An invalid MathText title must not prevent a fitted plot from being saved.""" + output_path = tmp_path / "fit.png" + + result = create_plot( + x=np.array([0.0, 1.0, 2.0]), + y=np.array([0.0, 1.0, 2.0]), + ux=np.zeros(3), + uy=np.zeros(3), + y_fitted=np.array([0.0, 1.0, 2.0]), + fit_name=r"$x\notacommand$", + x_name="x(m)", + y_name="y(m)", + plot_config={**PLOT_CONFIG, "show_title": True}, + output_path=output_path, + ) + + assert result == str(output_path) + assert output_path.exists()