"""Image class. It plots the data."""
from __future__ import annotations
import logging
from typing import Any, Unpack
import numpy as np
from matplotlib.axes import Axes
from matplotlib.collections import LineCollection, PathCollection, QuadMesh
from matplotlib.contour import QuadContourSet
from numpy.typing import ArrayLike
from pyPLUTO.amr import oplotbox
from pyPLUTO.imagefuncs.colorbar import ColorbarManager
from pyPLUTO.imagefuncs.contour import ContourManager
from pyPLUTO.imagefuncs.create_axes import CreateAxesManager
from pyPLUTO.imagefuncs.display import DisplayManager
from pyPLUTO.imagefuncs.figure import FigureManager
from pyPLUTO.imagefuncs.imagetools import ImageToolsManager
from pyPLUTO.imagefuncs.interactive import InteractiveManager
from pyPLUTO.imagefuncs.legend import LegendManager
from pyPLUTO.imagefuncs.plot import PlotManager
from pyPLUTO.imagefuncs.range import RangeManager
from pyPLUTO.imagefuncs.scatter import ScatterManager
from pyPLUTO.imagefuncs.set_axis import AxisManager
from pyPLUTO.imagefuncs.streamplot import StreamplotManager
from pyPLUTO.imagefuncs.zoom import ZoomManager
from pyPLUTO.imagekwargs import (
ColorbarKwargs,
ContourKwargs,
CreateAxesKwargs,
DisplayKwargs,
FigureKwargs,
LegendKwargs,
PlotKwargs,
ScatterKwargs,
SetAxisKwargs,
StreamplotKwargs,
TextKwargs,
ZoomKwargs,
)
from pyPLUTO.imagemixin import ImageMixin
from pyPLUTO.imagestate import ImageState
from pyPLUTO.utils.configure import set_text
from pyPLUTO.utils.inspector import track_kwargs
from pyPLUTO.utils.resolver import AttrResolver
logger = logging.getLogger(__name__)
[docs]
class Image(ImageMixin):
"""Description of the Image class.
The Image class is a facade for the different managers that handle the
various aspects of plotting, such as creating axes, displaying data, adding
legends, text, fieldlines, colorbars, and more. It provides a unified
interface for creating and managing plots in a figure. The attributes are
handled through the `ImageState` class, which is a dataclass that stores the
state of the image, such as the figure, axes, and other properties. The
`Image` class uses a mediator pattern to manage the interactions between the
different managers and the state.
"""
@track_kwargs
def __init__(
self,
text: bool | None = None,
_check: bool = True,
**kwargs: Unpack[FigureKwargs],
) -> None:
"""Initialize the Image class.
Ihat creates a new figure and sets the LaTeX conditions, as well as the
matplotlib style. Every Image is associated to a figure object and only
one in order to avoid confusion between images and figures. If you want
to create multiple figures, you have to create multiple Image objects.
Parameters
----------
- close: bool, default True
If True, the existing figure with the same window number is closed.
- fig: Figure | None, default None
The figure instance. If not None, the figure is used (only
if we need to associate an Image to an existing figure).
- figsize: list[float], default varies
Sets the figure size. The default is [6*sqrt(ncol), 5*sqrt(nrow)],
computed from the number of rows and columns (or [8,5] for a single
plot).
- fontsize: float, default 17.0
Sets the fontsize for all the axis components.
- fontweight: str, default 'normal'
The font weight for all the axis components.
- LaTeX: bool | str, default False
The LaTeX option. Is True is selected, the default LaTeX font
is used. If 'pgf' is selected, the pgf backend is used to save pdf
figures with minimal file size. If XeLaTeX is not installed and the
'pgf' option is selected, the LaTeX option True is used as backup
strategy.
- numcolors: int, default 10
The number of colors in the colorscheme. The default number is 10,
but the full list contains 24 colors (+ black or white).
- nwin: int, default 1
The window number.
- replace: bool, default False
If True, the existing figure with the same window is replaced.
- style: str, default 'default'
The style of the figure. Possible values are: 'seaborn', 'ggplot',
'fivethirtyeight', 'bmh', 'grayscale', 'dark_background', 'classic',
etc.
- suptitle: str, default None
Creates a figure title over all the subplots.
- suptitlesize: str | int, default 'large'
The figure title size.
- tight: bool, default True
Enables/disables tight layout options for the figure. In case of a
highly customized plot (e.g. ratios or space between rows and
columns) the option is set by default to False since that option
would not be available for standard matplotlib functions.
- text: bool | None, default None
Controls output verbosity. None (default) logs the window number at
INFO level. False silences all output. True enables full DEBUG
logging.
- withblack: bool, default False
If True, the black color is used as first color.
- withwhite: bool, default False
If True, the white color is used as first color.
Returns
-------
- None
Examples
--------
- Example #1: create an empty image
>>> import pyPLUTO as pp
>>> I = pp.Image()
- Example #2: create an image with the pgf backend
>>> import pyPLUTO as pp
>>> I = pp.Image(LaTeX="pgf")
- Example #3: create an image with the LaTeX option True
>>> import pyPLUTO as pp
>>> I = pp.Image(LaTeX=True)
- Example #4: create an image with fixed size
>>> import pyPLUTO as pp
>>> I = pp.Image(figsize=[5, 5])
- Example #5: create an image with a title
>>> import pyPLUTO as pp
>>> I = pp.Image(suptitle="Title")
"""
self.state = ImageState()
set_text(text)
self.FigureManager = FigureManager(self.state, **kwargs)
# Initialize managers
self.AxisManager = AxisManager(self.state)
self.ColorbarManager = ColorbarManager(self.state)
self.ContourManager = ContourManager(self.state)
self.CreateAxesManager = CreateAxesManager(self.state)
self.DisplayManager = DisplayManager(self.state)
self.ImageToolsManager = ImageToolsManager(self.state)
self.InteractiveManager = InteractiveManager(self.state)
self.LegendManager = LegendManager(self.state)
self.PlotManager = PlotManager(self.state)
self.RangeManager = RangeManager(self.state)
self.ScatterManager = ScatterManager(self.state)
self.StreamplotManager = StreamplotManager(self.state)
self.ZoomManager = ZoomManager(self.state)
if text is not False:
logger.info("Image class created at nwin %s", self.nwin)
def __repr__(self) -> str:
"""Return the repr of the Image class."""
return f"Image(nwin={self.nwin!r}, figsize={self.figsize!r})"
def __str__(self) -> str:
"""Print the Image class."""
return r"""
Image class.
It plots the data.
Image properties:
- Figure size (figsize)
- Window number (nwin)
- Number of subplots (nrow0 x ncol0)
- Global fontsize (fontsize)
Public methods available:
- create_axes
Adds a set of [nrow,ncol] subplots to the figure.
- colorbar
Places a colorbar in a subplot or next to a subplot.
- contour
Plots a contour plot in a subplot.
- display
Plots a 2D quantity in a subplot.
- interactive
Creates an interactive plot with a slider to change the data.
- legend
Places one legend in a subplot.
- set_axis
Changes the parameter of a specific subplot.
- plot
Plots one line in a subplot.
- savefig
Saves the figure in a file.
- scatter
Plots a scatter plot in a subplot.
- streamplot
Plots a stream plot in a subplot.
- text
Places the text in the figure or in a subplot.
- zoom
Creates an inset zoom region of a subplot.
Public attributes available:
- ax:
The list of relevant axes in the figure.
- fig
The figure associated to the image.
- fontsize
The fontsize in the figure.
- fontweight
The fontweight in the figure.
- nwin
The window number.
- tg
The tight layout of the figure.
Please do not use 'private' methods and attributes if not absolutely
necessary.
"""
def __getattr__(self, name: str) -> object:
"""Get the attribute of the Image class."""
val = getattr(self.state, name)
return AttrResolver.resolve(self.state, name, val)
def __setattr__(self, name: str, value: object) -> None:
"""Set the attribute of the Image class."""
if name == "state" or not hasattr(self, "state"):
return super().__setattr__(name, value)
return setattr(self.state, name, value)
def animate(
self,
gifname: str | None = None,
frames: int | None = None,
interval: int = 500,
updateslider: bool = True,
script_relative: bool = False,
) -> None:
"""Animate method."""
return self.InteractiveManager.animate(
gifname=gifname,
frames=frames,
interval=interval,
updateslider=updateslider,
script_relative=script_relative,
)
animate.__doc__ = InteractiveManager.animate.__doc__
def colorbar(
self,
pcm: QuadMesh
| PathCollection
| LineCollection
| QuadContourSet
| None = None,
axs: Axes | int | None = None,
cax: Axes | int | None = None,
_check: bool = True,
**kwargs: Unpack[ColorbarKwargs],
) -> None:
"""Colorbar method."""
return self.ColorbarManager.colorbar(
pcm=pcm,
axs=axs,
cax=cax,
_check=_check,
**kwargs,
)
colorbar.__doc__ = ColorbarManager.colorbar.__doc__
def contour(
self,
var: ArrayLike,
ax: Axes | list[Axes] | int | None = None,
_check: bool = True,
**kwargs: Unpack[ContourKwargs],
) -> QuadContourSet:
"""Contour method."""
return self.ContourManager.contour(var, ax, _check=_check, **kwargs)
contour.__doc__ = ContourManager.contour.__doc__
def create_axes(
self,
_check: bool = True,
**kwargs: Unpack[CreateAxesKwargs],
) -> Axes | list[Axes]:
"""Creation of a set of axes using add_subplot from matplotlib."""
return self.CreateAxesManager.create_axes(_check=_check, **kwargs)
create_axes.__doc__ = CreateAxesManager.create_axes.__doc__
def display(
self,
var: ArrayLike,
ax: Axes | list[Axes] | int | None = None,
_check: bool = True,
**kwargs: Unpack[DisplayKwargs],
) -> QuadMesh:
"""Display method."""
return self.DisplayManager.display(var, ax=ax, _check=_check, **kwargs)
display.__doc__ = DisplayManager.display.__doc__
def interactive(
self,
varx: dict[int, np.ndarray] | np.ndarray,
vary: dict[int, np.ndarray] | None = None,
_check: bool = True,
limfix: bool = True,
labslider: list[str | float] | None = None,
**kwargs: Unpack[DisplayKwargs],
) -> None:
"""Interactive method."""
return self.InteractiveManager.interactive(
varx=varx,
vary=vary,
_check=_check,
limfix=limfix,
labslider=labslider,
**kwargs,
)
interactive.__doc__ = InteractiveManager.interactive.__doc__
def legend(
self,
ax: Axes | int | None = None,
fromplot: bool = False,
_check: bool = True,
**kwargs: Unpack[LegendKwargs],
) -> None:
"""Legend method."""
return self.LegendManager.legend(
ax=ax,
fromplot=fromplot,
_check=_check,
**kwargs,
)
legend.__doc__ = LegendManager.legend.__doc__
def plot(
self,
x: ArrayLike,
y: ArrayLike | None = None,
ax: Axes | list[Axes] | int | None = None,
_check: bool = True,
**kwargs: Unpack[PlotKwargs],
) -> None:
"""Plot method."""
return self.PlotManager.plot(x, y, ax, _check=_check, **kwargs)
plot.__doc__ = PlotManager.plot.__doc__
def savefig(
self,
filename: str = "img.png",
bbox: str | None = "tight",
dpi: int = 300,
script_relative: bool = False,
) -> None:
"""Savefig method."""
return self.ImageToolsManager.savefig(
filename=filename,
bbox=bbox,
dpi=dpi,
script_relative=script_relative,
)
savefig.__doc__ = ImageToolsManager.savefig.__doc__
def scatter(
self,
x: np.ndarray | list[float],
y: np.ndarray | list[float],
ax: Axes | list[Axes] | int | None = None,
_check: bool = True,
**kwargs: Unpack[ScatterKwargs],
) -> PathCollection:
"""Scatter method."""
return self.ScatterManager.scatter(x, y, ax, _check=_check, **kwargs)
scatter.__doc__ = ScatterManager.scatter.__doc__
def set_axis(
self,
ax: Axes | list[Axes] | int | None = None,
_check: bool = True,
**kwargs: Unpack[SetAxisKwargs],
) -> None:
"""Set axis method."""
return self.AxisManager.set_axis(ax=ax, _check=_check, **kwargs)
set_axis.__doc__ = AxisManager.set_axis.__doc__
def streamplot(
self,
var1: np.ndarray,
var2: np.ndarray,
ax: Axes | list[Axes] | int | None = None,
_check: bool = True,
**kwargs: Unpack[StreamplotKwargs],
) -> LineCollection:
"""Streamplot method."""
return self.StreamplotManager.streamplot(
var1,
var2,
ax,
_check=_check,
**kwargs,
)
streamplot.__doc__ = StreamplotManager.streamplot.__doc__
def text(
self,
text: str,
x: float = 0.85,
y: float = 0.85,
ax: Axes | int | None = None,
c: str = "k",
_check: bool = True,
**kwargs: Unpack[TextKwargs],
) -> None:
"""Text method."""
return self.ImageToolsManager.text(
text=text,
x=x,
y=y,
ax=ax,
c=c,
_check=_check,
**kwargs,
)
text.__doc__ = ImageToolsManager.text.__doc__
def zoom(
self,
ax: Axes | list[Axes] | int | None = None,
_check: bool = True,
**kwargs: Unpack[ZoomKwargs],
) -> Axes:
"""Zoom method."""
return self.ZoomManager.zoom(ax=ax, _check=_check, **kwargs)
zoom.__doc__ = ZoomManager.zoom.__doc__
[docs]
@track_kwargs
def oplotbox(
self,
*args: Any, # noqa: ANN401
_check: bool = True,
**kwargs: object,
) -> None:
"""Plot a box in the figure (AMR, WIP)."""
oplotbox(self, *args, _check=False, **kwargs)