Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 62 additions & 20 deletions src/maxplotlib/canvas/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2139,6 +2139,10 @@ def _save_plotly(
"(e.g., `pip install -U kaleido`), or export to HTML instead."
) from exc

def _matplotlib_style(self):
"""The canvas style of the last Matplotlib drawing, as an ``rc_context``."""
return plt.rc_context(getattr(self, "_matplotlib_rc", None) or {})

def savefig(
self,
filename,
Expand Down Expand Up @@ -2171,7 +2175,8 @@ def savefig(
)
_fn = f"{filename_no_extension}_{layers}.{extension}"
savefig_kwargs = {"dpi": self.dpi} if self.dpi is not None else {}
fig.savefig(_fn, **savefig_kwargs)
with self._matplotlib_style():
fig.savefig(_fn, **savefig_kwargs)
print(f"Saved {_fn}")
else:
if layers is None:
Expand All @@ -2182,15 +2187,17 @@ def savefig(

if self._plotted:
savefig_kwargs = {"dpi": self.dpi} if self.dpi is not None else {}
self._matplotlib_fig.savefig(full_filepath, **savefig_kwargs)
with self._matplotlib_style():
self._matplotlib_fig.savefig(full_filepath, **savefig_kwargs)
else:
fig, axs = self._render(
backend="matplotlib",
savefig=True,
layers=layers,
)
savefig_kwargs = {"dpi": self.dpi} if self.dpi is not None else {}
fig.savefig(full_filepath, **savefig_kwargs)
with self._matplotlib_style():
fig.savefig(full_filepath, **savefig_kwargs)
if verbose:
print(f"Saved {full_filepath}")
elif backend == "plotext":
Expand Down Expand Up @@ -2422,12 +2429,13 @@ def show(
)
if verbose:
print("Displaying Matplotlib figure...")
if _display_matplotlib_figure_in_notebook(fig):
# IPython has rendered the figure already. Closing it prevents
# a later implicit pyplot display and releases its resources.
plt.close(fig)
else:
plt.show(block=block)
with self._matplotlib_style():
if _display_matplotlib_figure_in_notebook(fig):
# IPython has rendered the figure already. Closing it prevents
# a later implicit pyplot display and releases its resources.
plt.close(fig)
else:
plt.show(block=block)
return fig, axes
elif backend == "plotly":
resolved_usetex = self._usetex if usetex is None else usetex
Expand Down Expand Up @@ -2461,7 +2469,7 @@ def show(
else:
raise ValueError("Invalid backend")

def plot_matplotlib(
def _plot_matplotlib(
self,
savefig: bool = False,
layers: list | None = None,
Expand All @@ -2470,12 +2478,6 @@ def plot_matplotlib(
matplotlib_postprocess=None,
matplotlib_customizations=None,
):
"""
Generate and optionally display the subplots.

Parameters:
filename (str, optional): Filename to save the figure.
"""
if verbose:
print("Generating Matplotlib figure...")

Expand Down Expand Up @@ -2677,6 +2679,45 @@ def plot_matplotlib(
matplotlib_postprocess(fig, axes)
return fig, axes

def plot_matplotlib(
self,
savefig: bool = False,
layers: list | None = None,
usetex: bool | None = None,
verbose: bool = False,
matplotlib_postprocess=None,
matplotlib_customizations=None,
):
"""
Generate and optionally display the subplots.

The canvas style (fonts, ticks, grid) applies while drawing only:
Matplotlib's global ``rcParams`` are the same afterwards, so figures
drawn later by other code keep their own style. ``savefig`` and
``show`` apply the style again while saving and showing.

Parameters:
filename (str, optional): Filename to save the figure.
"""
before = dict(plt.rcParams)
with plt.rc_context():
result = self._plot_matplotlib(
savefig=savefig,
layers=layers,
usetex=usetex,
verbose=verbose,
matplotlib_postprocess=matplotlib_postprocess,
matplotlib_customizations=matplotlib_customizations,
)
# what the style changed, for saving and showing the figure later:
# e.g. LaTeX text reads the font family when it is drawn
self._matplotlib_rc = {
key: value
for key, value in plt.rcParams.items()
if key in before and before[key] != value
}
return result

def _validate_import_backend(self, backend, *, allow_unsupported=False):
"""Never silently drop native imported axes or figure decorations."""
if not hasattr(self, "import_report") or allow_unsupported:
Expand Down Expand Up @@ -2866,10 +2907,11 @@ def plot_plotly(
"twiny is currently supported only by the matplotlib backend"
)

setup_tex_fonts(
fontsize=self.fontsize,
usetex=resolved_usetex,
) # adjust or redefine for Plotly if needed
with plt.rc_context(): # Matplotlib's global style stays as it is
setup_tex_fonts(
fontsize=self.fontsize,
usetex=resolved_usetex,
) # adjust or redefine for Plotly if needed

# Create subplot titles in row-major order (Plotly expects rows*cols entries)
subplot_titles = [""] * (self.nrows * self.ncols)
Expand Down
55 changes: 55 additions & 0 deletions src/maxplotlib/tests/test_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -1323,3 +1323,58 @@ def test_get_tikz_figure_returns_native_figure():

if __name__ == "__main__":
test()


def test_matplotlib_rendering_keeps_the_global_style(tmp_path):
import matplotlib.pyplot as plt

from maxplotlib import Canvas

canvas = Canvas()
canvas.add_subplot().plot([0, 1], [0, 1])
style = dict(plt.rcParams)
fig, axes = canvas.plot_matplotlib()
# the canvas style applies to its own figure ...
assert axes[0, 0].xaxis.get_ticklabels()[0].get_family() == ["serif"]
assert axes[0, 0].xaxis.get_major_ticks()[0]._tickdir == "in"
# ... and not to Matplotlib's global style, nor to figures drawn later
assert dict(plt.rcParams) == style
other, ax = plt.subplots()
assert ax.xaxis.get_major_ticks()[0]._tickdir == style["xtick.direction"]
plt.close(other)
# saving applies the style again, then restores the global one
canvas.savefig(tmp_path / "canvas.png")
assert (tmp_path / "canvas.png").is_file()
assert dict(plt.rcParams) == style
plt.close(fig)


def test_saving_draws_with_the_canvas_style(monkeypatch, tmp_path):
import matplotlib.pyplot as plt

from maxplotlib import Canvas

canvas = Canvas()
canvas.add_subplot().plot([0, 1], [0, 1])
seen = {}

def fake_savefig(self, *args, **kwargs):
seen["family"] = list(plt.rcParams["font.family"])

monkeypatch.setattr(plt.Figure, "savefig", fake_savefig)
canvas.savefig(tmp_path / "canvas.png")
assert seen["family"] == ["serif"]


def test_plotly_rendering_keeps_the_global_style():
import matplotlib.pyplot as plt
import pytest

pytest.importorskip("plotly")
from maxplotlib import Canvas

canvas = Canvas()
canvas.add_subplot().plot([0, 1], [0, 1])
style = dict(plt.rcParams)
canvas.plot_plotly()
assert dict(plt.rcParams) == style
Loading