Skip to content

Commit f2a2952

Browse files
authored
Improve latex handling (#45)
* pass down usetex * Allow for setting MAXPLOTLIB_USETEX environment variable or a parameter for Canvas * formatting * pass verbose flag * Update docstrings
1 parent 9870f24 commit f2a2952

3 files changed

Lines changed: 163 additions & 14 deletions

File tree

.github/workflows/static_analysis.yml

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ on:
1111
- devel
1212

1313
jobs:
14-
static-analysis:
14+
cloc:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Checkout the code
@@ -24,38 +24,103 @@ jobs:
2424
./cloc --version
2525
./cloc $(git ls-files)
2626
27+
black:
28+
runs-on: ubuntu-latest
29+
steps:
30+
- name: Checkout the code
31+
uses: actions/checkout@v4
32+
33+
- name: Setup Python
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: "3.11"
37+
2738
- name: Code formatting with black
2839
run: |
29-
pip install black
3040
pip install "black[jupyter]"
3141
black --check src/
3242
black --check tutorials/
3343
44+
isort:
45+
runs-on: ubuntu-latest
46+
steps:
47+
- name: Checkout the code
48+
uses: actions/checkout@v4
49+
50+
- name: Setup Python
51+
uses: actions/setup-python@v5
52+
with:
53+
python-version: "3.11"
54+
3455
- name: Code formatting with isort
3556
run: |
3657
pip install isort
3758
isort --check src/
3859
isort --check tutorials/
3960
40-
- name: Code formatting with prospector
61+
mypy:
62+
runs-on: ubuntu-latest
63+
steps:
64+
- name: Checkout the code
65+
uses: actions/checkout@v4
66+
67+
- name: Setup Python
68+
uses: actions/setup-python@v5
69+
with:
70+
python-version: "3.11"
71+
72+
- name: Type check with mypy
4173
continue-on-error: true
4274
run: |
4375
pip install mypy
4476
mypy src/
4577
46-
- name: Code formatting with prospector
78+
prospector:
79+
runs-on: ubuntu-latest
80+
steps:
81+
- name: Checkout the code
82+
uses: actions/checkout@v4
83+
84+
- name: Setup Python
85+
uses: actions/setup-python@v5
86+
with:
87+
python-version: "3.11"
88+
89+
- name: Static analysis with prospector
4790
continue-on-error: true
4891
run: |
4992
pip install prospector
5093
prospector src/
51-
52-
- name: Code formatting with ruff
94+
95+
ruff:
96+
runs-on: ubuntu-latest
97+
steps:
98+
- name: Checkout the code
99+
uses: actions/checkout@v4
100+
101+
- name: Setup Python
102+
uses: actions/setup-python@v5
103+
with:
104+
python-version: "3.11"
105+
106+
- name: Lint with ruff
53107
continue-on-error: true
54108
run: |
55109
pip install ruff
56110
ruff check src/
57111
58-
- name: Code formatting with pylint
112+
pylint:
113+
runs-on: ubuntu-latest
114+
steps:
115+
- name: Checkout the code
116+
uses: actions/checkout@v4
117+
118+
- name: Setup Python
119+
uses: actions/setup-python@v5
120+
with:
121+
python-version: "3.11"
122+
123+
- name: Lint with pylint
59124
continue-on-error: true
60125
run: |
61126
pip install pylint

src/maxplotlib/canvas/canvas.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ def to_gridspec_kw(self) -> dict[str, float]:
3131
return {"wspace": self.wspace, "hspace": self.hspace}
3232

3333

34+
def _parse_bool_env_var(name: str, default: bool = False) -> bool:
35+
value = os.getenv(name)
36+
if value is None:
37+
return default
38+
return value.strip().lower() in {"1", "true", "yes", "on"}
39+
40+
3441
def plot_matplotlib(tikzfigure: TikzFigure, ax, layers=None):
3542
"""
3643
Plot all nodes and paths on the provided axis using Matplotlib.
@@ -179,6 +186,7 @@ def __init__(
179186
dpi: int = 300,
180187
width: str = "5cm",
181188
ratio: str = "golden", # TODO Add literal
189+
usetex: bool | None = None,
182190
subplot_spacing: SubplotSpacing | None = None,
183191
gridspec_kw: Mapping[str, float] | None = None,
184192
):
@@ -196,6 +204,8 @@ def __init__(
196204
dpi (int): DPI for the figure. Default is 300.
197205
width (str): Width of the figure. Default is "17cm".
198206
ratio (str): Aspect ratio. Default is "golden".
207+
usetex (bool | None): Default text.usetex behavior for this canvas.
208+
If None, read from MAXPLOTLIB_USETEX environment variable.
199209
subplot_spacing (SubplotSpacing): Typed subplot spacing.
200210
Default is SubplotSpacing(wspace=0.08, hspace=0.1).
201211
gridspec_kw (Mapping[str, float]): Optional matplotlib gridspec kwargs.
@@ -212,6 +222,11 @@ def __init__(
212222
self._dpi = dpi
213223
self._width = width
214224
self._ratio = ratio
225+
self._usetex = (
226+
_parse_bool_env_var("MAXPLOTLIB_USETEX", default=False)
227+
if usetex is None
228+
else usetex
229+
)
215230
if subplot_spacing is not None and gridspec_kw is not None:
216231
raise ValueError("Pass either subplot_spacing or gridspec_kw, not both.")
217232
if subplot_spacing is None and gridspec_kw is None:
@@ -764,34 +779,43 @@ def plot(
764779
backend: Backends = "matplotlib",
765780
savefig=False,
766781
layers=None,
782+
usetex: bool | None = None,
767783
verbose: bool = False,
768784
):
785+
resolved_usetex = self._usetex if usetex is None else usetex
786+
769787
if verbose:
770788
print(f"Plotting figure using backend: {backend}")
771789

772790
if backend == "matplotlib":
773791
return self.plot_matplotlib(
774792
savefig=savefig,
775793
layers=layers,
794+
usetex=resolved_usetex,
776795
verbose=verbose,
777796
)
778797
elif backend == "plotly":
779-
return self.plot_plotly(savefig=savefig)
798+
return self.plot_plotly(
799+
savefig=savefig,
800+
usetex=resolved_usetex,
801+
verbose=verbose,
802+
)
780803
elif backend == "plotext":
781804
return self.plot_plotext(
782805
savefig=savefig,
783806
layers=layers,
784807
verbose=verbose,
785808
)
786809
elif backend == "tikzfigure":
787-
return self.plot_tikzfigure(savefig=savefig)
810+
return self.plot_tikzfigure(savefig=savefig, verbose=verbose)
788811
else:
789812
raise ValueError(f"Invalid backend: {backend}")
790813

791814
def show(
792815
self,
793816
backend: Backends = "matplotlib",
794817
layers: list | None = None,
818+
usetex: bool | None = None,
795819
verbose: bool = False,
796820
):
797821
if verbose:
@@ -802,11 +826,13 @@ def show(
802826
backend="matplotlib",
803827
savefig=False,
804828
layers=layers,
829+
usetex=usetex,
805830
verbose=verbose,
806831
)
807832
# self._matplotlib_fig.show()
808833
elif backend == "plotly":
809-
self.plot_plotly(savefig=False)
834+
resolved_usetex = self._usetex if usetex is None else usetex
835+
self.plot_plotly(savefig=False, usetex=resolved_usetex)
810836
elif backend == "plotext":
811837
figure = self.plot_plotext(
812838
savefig=False,
@@ -827,7 +853,7 @@ def plot_matplotlib(
827853
self,
828854
savefig: bool = False,
829855
layers: list | None = None,
830-
usetex: bool = False,
856+
usetex: bool | None = None,
831857
verbose: bool = False,
832858
):
833859
"""
@@ -839,7 +865,8 @@ def plot_matplotlib(
839865
if verbose:
840866
print("Generating Matplotlib figure...")
841867

842-
tex_fonts = setup_tex_fonts(fontsize=self.fontsize, usetex=usetex)
868+
resolved_usetex = self._usetex if usetex is None else usetex
869+
tex_fonts = setup_tex_fonts(fontsize=self.fontsize, usetex=resolved_usetex)
843870

844871
setup_plotstyle(
845872
tex_fonts=tex_fonts,
@@ -1003,18 +1030,28 @@ def plot_plotext(
10031030
self._plotext_figure = wrapped
10041031
return wrapped
10051032

1006-
def plot_plotly(self, show=True, savefig=None, usetex=False):
1033+
def plot_plotly(
1034+
self,
1035+
show=True,
1036+
savefig=None,
1037+
usetex: bool | None = None,
1038+
verbose: bool = False,
1039+
):
10071040
"""
10081041
Generate and optionally display the subplots using Plotly.
10091042
10101043
Parameters:
10111044
show (bool): Whether to display the plot.
10121045
savefig (str, optional): Filename to save the figure if provided.
1046+
verbose (bool): Whether to print verbose output.
1047+
10131048
"""
10141049

1050+
resolved_usetex = self._usetex if usetex is None else usetex
1051+
10151052
setup_tex_fonts(
10161053
fontsize=self.fontsize,
1017-
usetex=usetex,
1054+
usetex=resolved_usetex,
10181055
) # adjust or redefine for Plotly if needed
10191056

10201057
# Set default width and height if not specified
@@ -1106,6 +1143,10 @@ def label(self):
11061143
def figsize(self):
11071144
return self._figsize
11081145

1146+
@property
1147+
def usetex(self):
1148+
return self._usetex
1149+
11091150
@property
11101151
def subplot_matrix(self):
11111152
return self._subplot_matrix

src/maxplotlib/tests/test_canvas.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,48 @@ def test_canvas_subplots_spacing_args_and_explicit_spacing_are_mutually_exclusiv
270270
)
271271

272272

273+
def test_canvas_usetex_reads_environment_default(monkeypatch):
274+
from maxplotlib import Canvas
275+
276+
monkeypatch.setenv("MAXPLOTLIB_USETEX", "true")
277+
canvas = Canvas()
278+
assert canvas.usetex is True
279+
280+
281+
def test_canvas_usetex_constructor_overrides_environment(monkeypatch):
282+
from maxplotlib import Canvas
283+
284+
monkeypatch.setenv("MAXPLOTLIB_USETEX", "true")
285+
canvas = Canvas(usetex=False)
286+
assert canvas.usetex is False
287+
288+
289+
def test_canvas_plot_usetex_precedence(monkeypatch):
290+
import matplotlib.pyplot as plt
291+
292+
import maxplotlib.canvas.canvas as canvas_module
293+
from maxplotlib import Canvas
294+
295+
captured: list[bool] = []
296+
297+
def fake_setup_tex_fonts(fontsize=14, usetex=False):
298+
captured.append(usetex)
299+
return {}
300+
301+
monkeypatch.setattr(canvas_module, "setup_tex_fonts", fake_setup_tex_fonts)
302+
303+
canvas = Canvas(usetex=True)
304+
subplot = canvas.add_subplot()
305+
subplot.plot([0, 1], [0, 1])
306+
307+
fig, _ = canvas.plot_matplotlib()
308+
plt.close(fig)
309+
310+
fig, _ = canvas.plot_matplotlib(usetex=False)
311+
plt.close(fig)
312+
313+
assert captured == [True, False]
314+
315+
273316
if __name__ == "__main__":
274317
test()

0 commit comments

Comments
 (0)