diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index e6282db..e3fa1e5 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -11,7 +11,7 @@ on: - devel jobs: - static-analysis: + cloc: runs-on: ubuntu-latest steps: - name: Checkout the code @@ -24,38 +24,103 @@ jobs: ./cloc --version ./cloc $(git ls-files) + black: + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Code formatting with black run: | - pip install black pip install "black[jupyter]" black --check src/ black --check tutorials/ + isort: + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Code formatting with isort run: | pip install isort isort --check src/ isort --check tutorials/ - - name: Code formatting with prospector + mypy: + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Type check with mypy continue-on-error: true run: | pip install mypy mypy src/ - - name: Code formatting with prospector + prospector: + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Static analysis with prospector continue-on-error: true run: | pip install prospector prospector src/ - - - name: Code formatting with ruff + + ruff: + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Lint with ruff continue-on-error: true run: | pip install ruff ruff check src/ - - name: Code formatting with pylint + pylint: + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Lint with pylint continue-on-error: true run: | pip install pylint diff --git a/src/maxplotlib/canvas/canvas.py b/src/maxplotlib/canvas/canvas.py index 0cc7f7f..658185e 100644 --- a/src/maxplotlib/canvas/canvas.py +++ b/src/maxplotlib/canvas/canvas.py @@ -31,6 +31,13 @@ def to_gridspec_kw(self) -> dict[str, float]: return {"wspace": self.wspace, "hspace": self.hspace} +def _parse_bool_env_var(name: str, default: bool = False) -> bool: + value = os.getenv(name) + if value is None: + return default + return value.strip().lower() in {"1", "true", "yes", "on"} + + def plot_matplotlib(tikzfigure: TikzFigure, ax, layers=None): """ Plot all nodes and paths on the provided axis using Matplotlib. @@ -179,6 +186,7 @@ def __init__( dpi: int = 300, width: str = "5cm", ratio: str = "golden", # TODO Add literal + usetex: bool | None = None, subplot_spacing: SubplotSpacing | None = None, gridspec_kw: Mapping[str, float] | None = None, ): @@ -196,6 +204,8 @@ def __init__( dpi (int): DPI for the figure. Default is 300. width (str): Width of the figure. Default is "17cm". ratio (str): Aspect ratio. Default is "golden". + usetex (bool | None): Default text.usetex behavior for this canvas. + If None, read from MAXPLOTLIB_USETEX environment variable. subplot_spacing (SubplotSpacing): Typed subplot spacing. Default is SubplotSpacing(wspace=0.08, hspace=0.1). gridspec_kw (Mapping[str, float]): Optional matplotlib gridspec kwargs. @@ -212,6 +222,11 @@ def __init__( self._dpi = dpi self._width = width self._ratio = ratio + self._usetex = ( + _parse_bool_env_var("MAXPLOTLIB_USETEX", default=False) + if usetex is None + else usetex + ) if subplot_spacing is not None and gridspec_kw is not None: raise ValueError("Pass either subplot_spacing or gridspec_kw, not both.") if subplot_spacing is None and gridspec_kw is None: @@ -764,8 +779,11 @@ def plot( backend: Backends = "matplotlib", savefig=False, layers=None, + usetex: bool | None = None, verbose: bool = False, ): + resolved_usetex = self._usetex if usetex is None else usetex + if verbose: print(f"Plotting figure using backend: {backend}") @@ -773,10 +791,15 @@ def plot( return self.plot_matplotlib( savefig=savefig, layers=layers, + usetex=resolved_usetex, verbose=verbose, ) elif backend == "plotly": - return self.plot_plotly(savefig=savefig) + return self.plot_plotly( + savefig=savefig, + usetex=resolved_usetex, + verbose=verbose, + ) elif backend == "plotext": return self.plot_plotext( savefig=savefig, @@ -784,7 +807,7 @@ def plot( verbose=verbose, ) elif backend == "tikzfigure": - return self.plot_tikzfigure(savefig=savefig) + return self.plot_tikzfigure(savefig=savefig, verbose=verbose) else: raise ValueError(f"Invalid backend: {backend}") @@ -792,6 +815,7 @@ def show( self, backend: Backends = "matplotlib", layers: list | None = None, + usetex: bool | None = None, verbose: bool = False, ): if verbose: @@ -802,11 +826,13 @@ def show( backend="matplotlib", savefig=False, layers=layers, + usetex=usetex, verbose=verbose, ) # self._matplotlib_fig.show() elif backend == "plotly": - self.plot_plotly(savefig=False) + resolved_usetex = self._usetex if usetex is None else usetex + self.plot_plotly(savefig=False, usetex=resolved_usetex) elif backend == "plotext": figure = self.plot_plotext( savefig=False, @@ -827,7 +853,7 @@ def plot_matplotlib( self, savefig: bool = False, layers: list | None = None, - usetex: bool = False, + usetex: bool | None = None, verbose: bool = False, ): """ @@ -839,7 +865,8 @@ def plot_matplotlib( if verbose: print("Generating Matplotlib figure...") - tex_fonts = setup_tex_fonts(fontsize=self.fontsize, usetex=usetex) + resolved_usetex = self._usetex if usetex is None else usetex + tex_fonts = setup_tex_fonts(fontsize=self.fontsize, usetex=resolved_usetex) setup_plotstyle( tex_fonts=tex_fonts, @@ -1003,18 +1030,28 @@ def plot_plotext( self._plotext_figure = wrapped return wrapped - def plot_plotly(self, show=True, savefig=None, usetex=False): + def plot_plotly( + self, + show=True, + savefig=None, + usetex: bool | None = None, + verbose: bool = False, + ): """ Generate and optionally display the subplots using Plotly. Parameters: show (bool): Whether to display the plot. savefig (str, optional): Filename to save the figure if provided. + verbose (bool): Whether to print verbose output. + """ + resolved_usetex = self._usetex if usetex is None else usetex + setup_tex_fonts( fontsize=self.fontsize, - usetex=usetex, + usetex=resolved_usetex, ) # adjust or redefine for Plotly if needed # Set default width and height if not specified @@ -1106,6 +1143,10 @@ def label(self): def figsize(self): return self._figsize + @property + def usetex(self): + return self._usetex + @property def subplot_matrix(self): return self._subplot_matrix diff --git a/src/maxplotlib/tests/test_canvas.py b/src/maxplotlib/tests/test_canvas.py index aa48efb..48f416e 100644 --- a/src/maxplotlib/tests/test_canvas.py +++ b/src/maxplotlib/tests/test_canvas.py @@ -270,5 +270,48 @@ def test_canvas_subplots_spacing_args_and_explicit_spacing_are_mutually_exclusiv ) +def test_canvas_usetex_reads_environment_default(monkeypatch): + from maxplotlib import Canvas + + monkeypatch.setenv("MAXPLOTLIB_USETEX", "true") + canvas = Canvas() + assert canvas.usetex is True + + +def test_canvas_usetex_constructor_overrides_environment(monkeypatch): + from maxplotlib import Canvas + + monkeypatch.setenv("MAXPLOTLIB_USETEX", "true") + canvas = Canvas(usetex=False) + assert canvas.usetex is False + + +def test_canvas_plot_usetex_precedence(monkeypatch): + import matplotlib.pyplot as plt + + import maxplotlib.canvas.canvas as canvas_module + from maxplotlib import Canvas + + captured: list[bool] = [] + + def fake_setup_tex_fonts(fontsize=14, usetex=False): + captured.append(usetex) + return {} + + monkeypatch.setattr(canvas_module, "setup_tex_fonts", fake_setup_tex_fonts) + + canvas = Canvas(usetex=True) + subplot = canvas.add_subplot() + subplot.plot([0, 1], [0, 1]) + + fig, _ = canvas.plot_matplotlib() + plt.close(fig) + + fig, _ = canvas.plot_matplotlib(usetex=False) + plt.close(fig) + + assert captured == [True, False] + + if __name__ == "__main__": test()