Source code for plopp.backends.matplotlib.tiled

# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)

from __future__ import annotations

from typing import Any

import numpy as np
from matplotlib import gridspec

from ...core.typing import FigureLike
from .figure import get_repr_maker
from .utils import is_interactive_backend, make_figure


[docs] class Tiled: """ A tiled figure. This is based on Matpotlib's GridSpec. .. versionadded:: 23.08.0 Parameters ---------- nrows: Number of rows. ncols: Number of columns. figsize: Figure size (width, height) in inches. **kwargs: Additional arguments passed to :class:`matplotlib.gridspec.GridSpec`. Examples -------- Create a tiled figure with two plots stacked vertically: >>> da1 = pp.data.data1d() >>> da2 = pp.data.data2d() >>> tiled = pp.tiled(2, 1) >>> tiled[0] = da1.plot() >>> tiled[1] = da2.plot() Create a tiled 2x2 figure: >>> da1 = pp.data.data1d() >>> da2 = pp.data.data2d() >>> da3 = pp.data.data2d() >>> da4 = pp.data.data1d() >>> tiled = pp.tiled(2, 2) >>> tiled[0, 0] = da1.plot() >>> tiled[0, 1] = da2.plot() >>> tiled[1, 0] = da3.plot() >>> tiled[1, 1] = da4.plot() Create a tiled figure with two figures side by side and the first is twice as wide: >>> da1 = pp.data.data1d() >>> da2 = pp.data.data2d() >>> tiled = pp.tiled(1, 3) >>> tiled[0, :2] = da1.plot() >>> tiled[0, 2] = da2.plot() """
[docs] def __init__( self, nrows: int, ncols: int, figsize: tuple[float, float] | None = None, **kwargs: Any, ) -> None: self.nrows = nrows self.ncols = ncols self.fig = make_figure( figsize=( (min(6.0 * ncols, 15.0), min(4.0 * nrows, 15.0)) if figsize is None else figsize ), layout='constrained', ) self.gs = gridspec.GridSpec(nrows, ncols, figure=self.fig, **kwargs) self.figures = np.full((nrows, ncols), None) self._history = []
def __setitem__( self, inds: int | slice | tuple[int, int] | tuple[slice, slice], fig: FigureLike, ) -> None: new_fig = fig.copy(ax=self.fig.add_subplot(self.gs[inds])) self.figures[inds] = new_fig self._history.append((inds, new_fig)) def __getitem__( self, inds: int | slice | tuple[int, int] | tuple[slice, slice] ) -> FigureLike: return self.figures[inds] def _repr_mimebundle_(self, include=None, exclude=None) -> dict: """ Mimebundle display representation for jupyter notebooks. """ if is_interactive_backend(): return self.fig.canvas._repr_mimebundle_(include=include, exclude=exclude) else: out = {'text/plain': f'TiledFigure(nrows={self.nrows}, ncols={self.ncols})'} npoints = sum( len(line.get_xdata()) for ax in self.fig.get_axes() for line in ax.lines ) out.update(get_repr_maker(npoints=npoints)(self.fig)) return out def save(self, filename: str, **kwargs: Any) -> None: """ Save the figure to file. The default directory for writing the file is the same as the directory where the script or notebook is running. Parameters ---------- filename: Name of the output file. Possible file extensions are ``.jpg``, ``.png``, ``.svg``, and ``.pdf``. """ self.fig.savefig(filename, **{**{'bbox_inches': 'tight'}, **kwargs}) def show(self) -> None: """ Make a call to Matplotlib's underlying ``show`` function. """ self.fig.show() def __add__(self, other: Tiled) -> Tiled: if not isinstance(other, self.__class__): t = Tiled(1, 1) t[0, 0] = other other = t out = Tiled(nrows=max(self.nrows, other.nrows), ncols=self.ncols + other.ncols) for inds, view in self._history: out[inds] = view for inds, view in other._history: out[inds[0], inds[1] + self.ncols] = view return out def __truediv__(self, other: Tiled) -> Tiled: if not isinstance(other, self.__class__): t = Tiled(1, 1) t[0, 0] = other other = t out = Tiled(nrows=self.nrows + other.nrows, ncols=max(self.ncols, other.ncols)) for inds, view in self._history: out[inds] = view for inds, view in other._history: out[inds[0] + self.nrows, inds[1]] = view return out
def hstack(left: Tiled | FigureLike, right: Tiled | FigureLike) -> Tiled: """ Display two views side by side. Parameters ---------- left: The view to display on the left. right: The view to display on the right. """ left_tiled = isinstance(left, Tiled) right_tiled = isinstance(right, Tiled) if (not left_tiled) and (not right_tiled): out = Tiled(1, 2) out[0, 0] = left out[0, 1] = right return out elif left_tiled: t = Tiled(1, 1) t[0, 0] = right return left + t else: t = Tiled(1, 1) t[0, 0] = left return t + right def vstack(top: Tiled | FigureLike, bottom: Tiled | FigureLike) -> Tiled: """ Display two views on top of each other. Parameters ---------- top: The view to display on the top. bottom: The view to display on the bottom. """ top_tiled = isinstance(top, Tiled) bottom_tiled = isinstance(bottom, Tiled) if (not top_tiled) and (not bottom_tiled): out = Tiled(2, 1) out[0, 0] = top out[1, 0] = bottom return out elif top_tiled: t = Tiled(1, 1) t[0, 0] = bottom return top / t else: t = Tiled(1, 1) t[0, 0] = top return t / bottom