from __future__ import annotations
from .inherit import INHERIT, Inherit, is_inherit
from copy import deepcopy
from dataclasses import dataclass
from typing import Callable, Optional, Protocol, runtime_checkable
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import Colormap, Normalize
from matplotlib.image import imread
from numpy.typing import ArrayLike
from scipy.interpolate import griddata
from .graph_elements import Plottable
try:
from typing import Self
except ImportError:
from typing_extensions import Self
@runtime_checkable
class Plottable2D(Plottable, Protocol):
"""
Dummy class to allow type hinting of Plottable2D objects.
"""
pass
[docs]
@dataclass
class Heatmap(Plottable2D):
"""
The class implements heatmaps.
Parameters
----------
image : ArrayLike | str
Image to display. If an array of values is given, the 2D array will be interpreted as the values of the image.
If a str if given, the corresponding file will be read as an image.
x_axis_range, y_axis_range : tuple[float, float], optional
The range of x and y values used for the axes as tuples containing the start and end of the range. These values
are ignored when ``x_mesh`` and ``y_mesh`` are provided.
x_mesh, y_mesh : ArrayLike, optional
Mesh grids defining the coordinates of the heatmap values. When provided, the heatmap is plotted using
``pcolormesh`` instead of ``imshow``.
color_map : str, Colormap
The color map to use for the :class:`~graphinglib.data_plotting_2d.Heatmap`. Can either be specified as a
string (named colormap from Matplotlib) or a Colormap object.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
color_map_range: tuple[float, float], optional
The data range covered by the color map, given as ``(minimum, maximum)``.
show_color_bar : bool
Whether or not to display the color bar next to the plot.
Defaults to ``True``.
alpha : float
Opacity value of the :class:`~graphinglib.data_plotting_2d.Heatmap`.
Range is ``0`` (transparent) to ``1`` (opaque).
Defaults to 1.0.
aspect_ratio : str or float
Aspect ratio of the axes. This value is ignored when ``x_mesh`` and ``y_mesh`` are provided.
Values include ``"auto"``, ``"equal"``, or a positive float.
Default depends on the ``figure_style`` configuration.
origin_position : str
Position of the origin of the axes (upper left or lower left corner). This value is ignored when ``x_mesh`` and
``y_mesh`` are provided.
Values are ``"upper"`` and ``"lower"``.
Default depends on the ``figure_style`` configuration.
interpolation : str
Interpolation method to be applied to the image. This value is ignored when ``x_mesh`` and ``y_mesh`` are
provided.
Values include ``"none"``, ``"nearest"``, ``"bilinear"``, ``"bicubic"``, ``"spline16"``,
``"spline36"``, ``"hanning"``, ``"hamming"``, ``"hermite"``, ``"kaiser"``, ``"quadric"``,
``"catrom"``, ``"gaussian"``, ``"bessel"``, ``"mitchell"``, ``"sinc"``, and ``"lanczos"``.
Defaults to ``"none"``.
.. seealso::
For other interpolation methods, refer to
`Interpolations for imshow <https://matplotlib.org/stable/gallery/images_contours_and_fields/interpolation_methods.html>`_.
norm : str or Normalize, optional
Normalization of the colormap. Default is ``None``.
"""
[docs]
def __init__(
self,
image: ArrayLike | str,
x_axis_range: Optional[tuple[float, float]] = None,
y_axis_range: Optional[tuple[float, float]] = None,
x_mesh: Optional[ArrayLike] = None,
y_mesh: Optional[ArrayLike] = None,
color_map: str | Colormap | Inherit = INHERIT,
color_map_range: Optional[tuple[float, float]] = None,
show_color_bar: bool | Inherit = INHERIT,
alpha: float = 1.0,
aspect_ratio: str | float | Inherit = INHERIT,
origin_position: str | Inherit = INHERIT,
interpolation: str = "none",
norm: Optional[str | Normalize] = None,
) -> None:
"""
The class implements heatmaps.
Parameters
----------
image : ArrayLike | str
Image to display. If an array of values is given, the 2D array will be interpreted as the values of the
image. If a str if given, the corresponding file will be read as an image.
x_axis_range, y_axis_range : tuple[float, float], optional
The range of x and y values used for the axes as tuples containing the start and end of the range. These
values are ignored when ``x_mesh`` and ``y_mesh`` are provided.
x_mesh, y_mesh : ArrayLike, optional
Mesh grids defining the coordinates of the heatmap values. When provided, the heatmap is plotted using
``pcolormesh`` instead of ``imshow``.
color_map : str, Colormap
The color map to use for the :class:`~graphinglib.data_plotting_2d.Heatmap`. Can either be specified as a
string (named colormap from Matplotlib) or a Colormap object.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
color_map_range: tuple[float, float], optional
The data range covered by the color map, given as ``(minimum, maximum)``.
show_color_bar : bool
Whether or not to display the color bar next to the plot.
Defaults to ``True``.
alpha : float
Opacity value of the :class:`~graphinglib.data_plotting_2d.Heatmap`.
Range is ``0`` (transparent) to ``1`` (opaque).
Defaults to 1.0.
aspect_ratio : str or float
Aspect ratio of the axes. This value is ignored when ``x_mesh`` and ``y_mesh`` are provided.
Values include ``"auto"``, ``"equal"``, or a positive float.
Default depends on the ``figure_style`` configuration.
origin_position : str
Position of the origin of the axes (upper left or lower left corner). This value is ignored when ``x_mesh``
and ``y_mesh`` are provided.
Values are ``"upper"`` and ``"lower"``.
Default depends on the ``figure_style`` configuration.
interpolation : str
Interpolation method to be applied to the image. This value is ignored when ``x_mesh`` and ``y_mesh`` are
provided.
Values include ``"none"``, ``"nearest"``, ``"bilinear"``, ``"bicubic"``, ``"spline16"``,
``"spline36"``, ``"hanning"``, ``"hamming"``, ``"hermite"``, ``"kaiser"``, ``"quadric"``,
``"catrom"``, ``"gaussian"``, ``"bessel"``, ``"mitchell"``, ``"sinc"``, and ``"lanczos"``.
Defaults to ``"none"``.
.. seealso::
For other interpolation methods, refer to
`Interpolations for imshow <https://matplotlib.org/stable/gallery/images_contours_and_fields/interpolation_methods.html>`_.
norm : str or Normalize, optional
Normalization of the colormap. Default is ``None``.
"""
self.show_color_bar = show_color_bar
self.image = image
self.x_axis_range = x_axis_range
self.y_axis_range = y_axis_range
self.x_mesh = x_mesh
self.y_mesh = y_mesh
self.color_map = color_map
self.color_map_range = color_map_range
self.alpha = alpha
self.aspect_ratio = aspect_ratio
self.origin_position = origin_position
self.interpolation = interpolation
self._norm = norm
self._color_bar_params: dict = {}
[docs]
@classmethod
def from_function(
cls,
func: Callable[[ArrayLike, ArrayLike], ArrayLike],
x_axis_range: tuple[float, float],
y_axis_range: tuple[float, float],
color_map: str | Colormap | Inherit = INHERIT,
color_map_range: Optional[tuple[float, float]] = None,
show_color_bar: bool = True,
alpha: float = 1.0,
aspect_ratio: str | float | Inherit = INHERIT,
origin_position: str | Inherit = INHERIT,
interpolation: str = "none",
number_of_points: tuple[int, int] = (50, 50),
norm: Optional[str | Normalize] = None,
) -> Self:
"""
Creates a heatmap from a function.
Parameters
----------
func : Callable[[ArrayLike, ArrayLike], ArrayLike]
Function to be plotted. Works with regular functions and lambda functions.
x_axis_range, y_axis_range : tuple[float, float], optional
The range of x and y values used for the axes as tuples containing the start and end of the range.
color_map : str, Colormap
The color map to use for the :class:`~graphinglib.data_plotting_2d.Heatmap`. Can either be specified as a
string (named colormap from Matplotlib) or a Colormap object.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
color_map_range: tuple[float, float], optional
The data range covered by the color map, given as ``(minimum, maximum)``.
show_color_bar : bool
Whether or not to display the color bar next to the plot.
Defaults to ``True``.
alpha : float
Opacity value of the :class:`~graphinglib.data_plotting_2d.Heatmap`.
Range is ``0`` (transparent) to ``1`` (opaque).
Defaults to 1.0.
aspect_ratio : str or float
Aspect ratio of the axes.
Values include ``"auto"``, ``"equal"``, or a positive float.
Default depends on the ``figure_style`` configuration.
origin_position : str
Position of the origin of the axes (upper left or lower left corner).
Values are ``"upper"`` and ``"lower"``.
Default depends on the ``figure_style`` configuration.
interpolation : str
Interpolation method to be applied to the image.
Values include ``"none"``, ``"nearest"``, ``"bilinear"``, ``"bicubic"``, ``"spline16"``,
``"spline36"``, ``"hanning"``, ``"hamming"``, ``"hermite"``, ``"kaiser"``, ``"quadric"``,
``"catrom"``, ``"gaussian"``, ``"bessel"``, ``"mitchell"``, ``"sinc"``, and ``"lanczos"``.
Defaults to ``"none"``.
.. seealso::
For other interpolation methods, refer to
`Interpolations for imshow <https://matplotlib.org/stable/gallery/images_contours_and_fields/interpolation_methods.html>`_.
number_of_points : tuple[int, int]
Number of points in the x and y coordinates.
Defaults to ``(50, 50)``.
norm : str or Normalize, optional
Normalization of the colormap. Default is ``None``.
Returns
-------
A :class:`~graphinglib.data_plotting_2d.Heatmap` object created from a function.
"""
x = np.linspace(x_axis_range[0], x_axis_range[1], number_of_points[0])
y = np.linspace(y_axis_range[0], y_axis_range[1], number_of_points[1])
x_grid, y_grid = np.meshgrid(x, y)
z = func(x_grid, y_grid)
return cls(
image=z,
x_axis_range=x_axis_range,
y_axis_range=y_axis_range,
color_map=color_map,
color_map_range=color_map_range,
show_color_bar=show_color_bar,
alpha=alpha,
aspect_ratio=aspect_ratio,
origin_position=origin_position,
interpolation=interpolation,
norm=norm,
)
[docs]
@classmethod
def from_points(
cls,
points: ArrayLike,
values: ArrayLike,
x_axis_range: tuple[float, float],
y_axis_range: tuple[float, float],
grid_interpolation: str = "nearest",
fill_value: float = np.nan,
color_map: str | Colormap | Inherit = INHERIT,
color_map_range: Optional[tuple[float, float]] = None,
show_color_bar: bool = True,
alpha: float = 1.0,
aspect_ratio: str | float | Inherit = INHERIT,
origin_position: str | Inherit = INHERIT,
interpolation: str = "none",
number_of_points: tuple[int, int] = (50, 50),
norm: Optional[str | Normalize] = None,
) -> Self:
"""
Creates a heatmap by interpolating unevenly distributed data points on a grid.
Parameters
----------
points : ArrayLike
The list or array of points at which values are known.
values : ArrayLike
The list or array of values at given points.
x_axis_range, y_axis_range : tuple[float, float], optional
The range of x and y values used for the axes as tuples containing the start and end of the range.
grid_interpolation : str
Interpolation method to be used when interpolating the uneavenly distributed data on a grid.
Values are ``"nearest"``, ``"linear"``, and ``"cubic"``.
color_map : str, Colormap
The color map to use for the :class:`~graphinglib.data_plotting_2d.Heatmap`. Can either be specified as a
string (named colormap from Matplotlib) or a Colormap object.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
color_map_range: tuple[float, float], optional
The data range covered by the color map, given as ``(minimum, maximum)``.
show_color_bar : bool
Whether or not to display the color bar next to the plot.
Defaults to ``True``.
alpha : float
Opacity value of the :class:`~graphinglib.data_plotting_2d.Heatmap`.
Range is ``0`` (transparent) to ``1`` (opaque).
Defaults to 1.0.
aspect_ratio : str or float
Aspect ratio of the axes.
Values include ``"auto"``, ``"equal"``, or a positive float.
Default depends on the ``figure_style`` configuration.
origin_position : str
Position of the origin of the axes (upper left or lower left corner).
Values are ``"upper"`` and ``"lower"``.
Default depends on the ``figure_style`` configuration.
interpolation : str
Interpolation method to be applied to the image.
Values include ``"none"``, ``"nearest"``, ``"bilinear"``, ``"bicubic"``, ``"spline16"``,
``"spline36"``, ``"hanning"``, ``"hamming"``, ``"hermite"``, ``"kaiser"``, ``"quadric"``,
``"catrom"``, ``"gaussian"``, ``"bessel"``, ``"mitchell"``, ``"sinc"``, and ``"lanczos"``.
Defaults to ``"none"``.
.. seealso::
For other interpolation methods, refer to
`Interpolations for imshow <https://matplotlib.org/stable/gallery/images_contours_and_fields/interpolation_methods.html>`_.
number_of_points : tuple[int, int]
Number of points in the x and y coordinates.
Defaults to ``(50, 50)``.
norm : str or Normalize, optional
Normalization of the colormap. Default is ``None``.
Returns
-------
A :class:`~graphinglib.data_plotting_2d.Heatmap` object created from data points.
"""
x = np.linspace(x_axis_range[0], x_axis_range[1], number_of_points[0])
y = np.linspace(y_axis_range[0], y_axis_range[1], number_of_points[1])
x_grid, y_grid = np.meshgrid(x, y)
grid = griddata(
points,
values,
(x_grid, y_grid),
method=grid_interpolation,
fill_value=fill_value,
)
return cls(
image=grid,
x_axis_range=x_axis_range,
y_axis_range=y_axis_range,
color_map=color_map,
color_map_range=color_map_range,
show_color_bar=show_color_bar,
alpha=alpha,
aspect_ratio=aspect_ratio,
origin_position=origin_position,
interpolation=interpolation,
norm=norm,
)
@property
def image(self) -> ArrayLike | str:
return self._image
@image.setter
def image(self, image: ArrayLike | str) -> None:
if isinstance(image, str):
self._image = imread(image)
self._show_color_bar = False
else:
self._image = np.asarray(image)
@property
def x_axis_range(self) -> Optional[tuple[float, float]]:
return self._x_axis_range
@x_axis_range.setter
def x_axis_range(self, x_axis_range: Optional[tuple[float, float]]) -> None:
self._x_axis_range = x_axis_range
@property
def y_axis_range(self) -> Optional[tuple[float, float]]:
return self._y_axis_range
@y_axis_range.setter
def y_axis_range(self, y_axis_range: Optional[tuple[float, float]]) -> None:
self._y_axis_range = y_axis_range
@property
def x_mesh(self) -> Optional[ArrayLike]:
return self._x_mesh
@x_mesh.setter
def x_mesh(self, x_mesh: Optional[ArrayLike]) -> None:
self._x_mesh = None if x_mesh is None else np.asarray(x_mesh)
@property
def y_mesh(self) -> Optional[ArrayLike]:
return self._y_mesh
@y_mesh.setter
def y_mesh(self, y_mesh: Optional[ArrayLike]) -> None:
self._y_mesh = None if y_mesh is None else np.asarray(y_mesh)
@property
def color_map(self) -> str | Colormap:
return self._color_map
@color_map.setter
def color_map(self, color_map: str | Colormap) -> None:
self._color_map = color_map
@property
def color_map_range(self) -> tuple[float, float]:
return self._color_map_range
@color_map_range.setter
def color_map_range(self, color_map_range: tuple[float, float]) -> None:
self._color_map_range = color_map_range
@property
def show_color_bar(self) -> bool:
return self._show_color_bar
@show_color_bar.setter
def show_color_bar(self, show_color_bar: bool) -> None:
self._show_color_bar = show_color_bar
@property
def alpha(self) -> float:
return self._alpha
@alpha.setter
def alpha(self, alpha: float) -> None:
self._alpha = alpha
@property
def aspect_ratio(self) -> str | float:
return self._aspect_ratio
@aspect_ratio.setter
def aspect_ratio(self, aspect_ratio: str | float) -> None:
self._aspect_ratio = aspect_ratio
@property
def origin_position(self) -> str:
return self._origin_position
@origin_position.setter
def origin_position(self, origin_position: str) -> None:
self._origin_position = origin_position
@property
def interpolation(self) -> str:
return self._interpolation
@interpolation.setter
def interpolation(self, interpolation: str) -> None:
self._interpolation = interpolation
@property
def color_bar_params(self) -> dict:
return self._color_bar_params
@property
def _xy_range(self) -> Optional[tuple[float, float, float, float]]:
if self._x_axis_range is not None and self._y_axis_range is not None:
return self._x_axis_range + self._y_axis_range
return None
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.data_plotting_2d.Heatmap`.
"""
return deepcopy(self)
[docs]
def set_color_bar_params(
self,
label: Optional[str] = None,
position: Optional[str] = None,
**color_bar_params,
) -> None:
"""
Sets the color bar parameters.
Parameters
----------
label : str, optional
Label of the color bar.
position : str, optional
Position of the color bar relative to the ``Figure``. It can be "left",
"right", "top" or "bottom". This also determines the orientation of the
color bar (vertical if the color bar is plotted on the "left" or "right",
horizontal otherwise). If None, the color bar is plotted on the right
side of the ``Figure``.
Values are ``"left"``, ``"right"``, ``"top"``, and ``"bottom"``.
**color_bar_params:
Additional keyword arguments are passed to ``plt.colorbar`` call.
"""
self._color_bar_params = color_bar_params
if label is not None:
self._color_bar_params["label"] = label
if position is not None:
self._color_bar_params["location"] = position
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None:
"""
Plots the element in the specified
`Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_.
"""
params = {
"cmap": self._color_map,
"alpha": self._alpha,
"norm": self._norm,
}
if self._color_map_range:
params["vmin"] = min(self._color_map_range)
params["vmax"] = max(self._color_map_range)
use_pcolormesh = self._x_mesh is not None and self._y_mesh is not None
if use_pcolormesh:
params = {k: v for k, v in params.items() if v != INHERIT}
image = axes.pcolormesh(
self._x_mesh,
self._y_mesh,
self._image,
zorder=z_order,
**params,
)
else:
params.update(
{
"aspect": self._aspect_ratio,
"origin": self._origin_position,
"interpolation": self._interpolation,
"extent": self._xy_range,
}
)
params = {k: v for k, v in params.items() if v != INHERIT}
image = axes.imshow(
self._image,
zorder=z_order,
**params,
)
fig = axes.get_figure()
if self._show_color_bar:
fig.colorbar(image, ax=axes, **self._color_bar_params)
[docs]
@dataclass
class VectorField(Plottable2D):
"""
This class implements vector fields.
Parameters
----------
x_data, y_data : ArrayLike
x and y coordinates of the vectors.
u_data, v_data : ArrayLike
Magnitudes in the x and y coordinates.
arrow_width : float
Width of the arrow shaft. Acts as a multiplier for the standard arrow width.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
arrow_head_size : float
Size of the arrow head. Acts as a multiplier for the standard arrow head size.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
scale : float
Scaling of the arrow lengths. If ``None``, the arrows will be automatically scaled to look nice. Use 1 for no scaling.
angle_in_data_coords : bool
Whether to use the screen coordinates or the data coordinates to determine the vector directions.
Defaults to ``True``.
color : str
Color of the vector arrows.
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
"""
[docs]
def __init__(
self,
x_data: ArrayLike,
y_data: ArrayLike,
u_data: ArrayLike,
v_data: ArrayLike,
arrow_width: float | Inherit = INHERIT,
arrow_head_size: float | Inherit = INHERIT,
scale: Optional[float] = None,
make_angles_axes_independent: bool = False,
color: str | Inherit = INHERIT,
) -> None:
"""
This class implements vector fields.
Parameters
----------
x_data, y_data : ArrayLike
x and y coordinates of the vectors.
u_data, v_data : ArrayLike
Magnitudes in the x and y coordinates.
arrow_width : float
Width of the arrow shaft. Acts as a multiplier for the standard arrow width.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
arrow_head_size : float
Size of the arrow head. Acts as a multiplier for the standard arrow head size.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
scale : float
Scaling of the arrow lengths. If ``None``, the arrows will be automatically scaled to look nice. Use 1 for no scaling.
Default is ``None``.
make_angles_axes_independent : bool
Whether to use the screen coordinates or the data coordinates to
determine the vector directions. If ``True``, vectors with u = v will
always appear as 45 degree angles with respect to the screen, regardless
of the axes limits. If ``False``, the vectors will scale with the
aspect ratio of the axes.
Defaults to ``False``.
color : str
Color of the vector arrows.
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
"""
self._x_data = np.asarray(x_data)
self._y_data = np.asarray(y_data)
self._u_data = np.asarray(u_data)
self._v_data = np.asarray(v_data)
self._arrow_width = arrow_width
self._arrow_head_size = arrow_head_size
self._scale = scale
self._make_angles_axes_independent = make_angles_axes_independent
self._color = color
[docs]
@classmethod
def from_function(
cls,
func: Callable[[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]],
x_axis_range: tuple[float, float],
y_axis_range: tuple[float, float],
number_of_arrows_x: int = 10,
number_of_arrows_y: int = 10,
arrow_width: float | Inherit = INHERIT,
arrow_head_size: float | Inherit = INHERIT,
scale: Optional[float] = None,
make_angles_axes_independent: bool = False,
color: str | Inherit = INHERIT,
) -> Self:
"""
Creates a :class:`~graphinglib.data_plotting_2d.VectorField` from a function.
Parameters
----------
func : Callable[[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]
Function to be plotted. Works with regular functions and lambda functions.
x_axis_range, y_axis_range : tuple[float, float], optional
The range of x and y values used for the axes as tuples containing the start and end of the range.
number_of_arrows_x, number_of_arrows_y : int
Number of arrows to plot in the x and y direction. Defaults to 10.
arrow_width : float
Width of the arrow shaft. Acts as a multiplier for the standard arrow width.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
arrow_head_size : float
Size of the arrow head. Acts as a multiplier for the standard arrow head size.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
scale : float
Scaling of the arrow lengths. If ``None``, the arrows will be automatically scaled to look nice. Use 1 for no scaling.
Default is ``None``.
make_angles_axes_independent : bool
Whether to use the screen coordinates or the data coordinates to
determine the vector directions. If ``True``, vectors with u = v will
always appear as 45 degree angles with respect to the screen, regardless
of the axes limits. If ``False``, the vectors will scale with the
aspect ratio of the axes.
Defaults to ``False``.
color : str
Color of the vector arrows.
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Returns
-------
A :class:`~graphinglib.data_plotting_2d.VectorField` object from a function.
"""
x = np.linspace(x_axis_range[0], x_axis_range[-1], number_of_arrows_x)
y = np.linspace(y_axis_range[0], y_axis_range[-1], number_of_arrows_y)
x_grid, y_grid = np.meshgrid(x, y)
u, v = func(x_grid, y_grid)
return cls(
x_grid,
y_grid,
u,
v,
arrow_width,
arrow_head_size,
scale,
make_angles_axes_independent,
color,
)
@property
def x_data(self) -> ArrayLike:
return self._x_data
@x_data.setter
def x_data(self, x_data: ArrayLike) -> None:
self._x_data = np.asarray(x_data)
@property
def y_data(self) -> ArrayLike:
return self._y_data
@y_data.setter
def y_data(self, y_data: ArrayLike) -> None:
self._y_data = np.asarray(y_data)
@property
def u_data(self) -> ArrayLike:
return self._u_data
@u_data.setter
def u_data(self, u_data: ArrayLike) -> None:
self._u_data = np.asarray(u_data)
@property
def v_data(self) -> ArrayLike:
return self._v_data
@v_data.setter
def v_data(self, v_data: ArrayLike) -> None:
self._v_data = np.asarray(v_data)
@property
def arrow_width(self) -> float:
return self._arrow_width
@arrow_width.setter
def arrow_width(self, arrow_width: float) -> None:
self._arrow_width = arrow_width
@property
def arrow_head_size(self) -> float:
return self._arrow_head_size
@arrow_head_size.setter
def arrow_head_size(self, arrow_head_size: float) -> None:
self._arrow_head_size = arrow_head_size
@property
def scale(self) -> Optional[float]:
return self._scale
@scale.setter
def scale(self, scale: Optional[float]) -> None:
self._scale = scale
@property
def make_angles_axes_independent(self) -> bool:
return self._make_angles_axes_independent
@make_angles_axes_independent.setter
def make_angles_axes_independent(self, value: bool) -> None:
self._make_angles_axes_independent = value
@property
def color(self) -> str:
return self._color
@color.setter
def color(self, color: str) -> None:
self._color = color
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.data_plotting_2d.VectorField`.
"""
return deepcopy(self)
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None:
"""
Plots the element in the specified
`Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_.
"""
if self._make_angles_axes_independent:
angle = "uv"
else:
angle = "xy"
params = {
"angles": angle,
"width": 0.005 * self._arrow_width,
"headwidth": 4 * self._arrow_head_size / self._arrow_width,
"headlength": 4 * self._arrow_head_size / self._arrow_width,
"headaxislength": 4 * self._arrow_head_size / self._arrow_width,
"color": self._color,
"scale": 1 / self._scale if self._scale is not None else None,
"scale_units": "xy",
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.quiver(
self._x_data,
self._y_data,
self._u_data,
self._v_data,
zorder=z_order,
**params,
)
[docs]
@dataclass
class Contour(Plottable2D):
"""
This class implements contour plots.
Parameters
----------
z_data : ArrayLike
Data for each point of the mesh.
x_mesh, y_mesh : ArrayLike, optional
Mesh grids defining the coordinates of the contour values. If not provided, a mesh grid will be created based on
the shape of ``z_data``.
levels : int | ArrayLike
If `levels` is an integer, it defines the number of levels to use in the contour.
If `levels` is an array, it defines the value of each contour level.
Typical range is ``5`` to ``20`` when given as an integer.
Default depends on the ``figure_style`` configuration.
color_map : str or Colormap
The color map to use for the :class:`~graphinglib.data_plotting_2d.Contour`. Can either be specified as a
string (named colormap from Matplotlib) or a Colormap object.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
color_map_range: tuple[float, float], optional
The data range covered by the color map, given as ``(minimum, maximum)``.
show_color_bar : bool
Whether or not to display the color bar next to the plot.
Default depends on the ``figure_style`` configuration.
filled : bool
Wheter or not to fill the contour with color.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the filled contour.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
line_widths : float | ArrayLike
If the contour is not filled, the width of the contour lines. If an array is provided, it defines the line width
for each contour level.
Typical range is ``0.5`` to ``3`` points.
Default depends on the ``figure_style`` configuration.
"""
_z_data: ArrayLike
_x_mesh: ArrayLike
_y_mesh: ArrayLike
_levels: int | Inherit = INHERIT
_color_map: str | Colormap | Inherit = INHERIT
_show_color_bar: bool | Inherit = INHERIT
_filled: bool | Inherit = INHERIT
_alpha: float | Inherit = INHERIT
_line_widths: float | ArrayLike | Inherit = INHERIT
[docs]
def __init__(
self,
z_data: ArrayLike,
x_mesh: Optional[ArrayLike] = None,
y_mesh: Optional[ArrayLike] = None,
levels: int | ArrayLike | Inherit = INHERIT,
color_map: str | Colormap | Inherit = INHERIT,
color_map_range: Optional[tuple[float, float]] = None,
show_color_bar: bool | Inherit = INHERIT,
filled: bool | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
line_widths: float | ArrayLike | Inherit = INHERIT,
) -> None:
"""
This class implements contour plots.
Parameters
----------
z_data : ArrayLike
Data for each point of the mesh.
x_mesh, y_mesh : ArrayLike, optional
Mesh grids defining the coordinates of the contour values. If not provided, a mesh grid will be created
based on the shape of ``z_data``.
levels : int | ArrayLike
If `levels` is an integer, it defines the number of levels to use in the contour.
If `levels` is an array, it defines the value of each contour level.
Typical range is ``5`` to ``20`` when given as an integer.
Default depends on the ``figure_style`` configuration.
color_map : str or Colormap
The color map to use for the :class:`~graphinglib.data_plotting_2d.Contour`. Can either be specified as a
string (named colormap from Matplotlib) or a Colormap object.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
color_map_range: tuple[float, float], optional
The data range covered by the color map, given as ``(minimum, maximum)``.
show_color_bar : bool
Whether or not to display the color bar next to the plot.
Default depends on the ``figure_style`` configuration.
filled : bool
Wheter or not to fill the contour with color.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the filled contour.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
line_widths : float | ArrayLike
If the contour is not filled, the width of the contour lines. If an array is provided, it defines the line
width for each contour level.
Typical range is ``0.5`` to ``3`` points.
Default depends on the ``figure_style`` configuration.
"""
self.z_data = z_data
self.x_mesh = x_mesh
self.y_mesh = y_mesh
self._levels = levels
self._color_map = color_map
self._color_map_range = color_map_range
self._show_color_bar = show_color_bar
self._filled = filled
self._alpha = alpha
self._line_widths = line_widths
self._color_bar_params: dict = {}
[docs]
@classmethod
def from_function(
cls,
func: Callable[[ArrayLike, ArrayLike], ArrayLike],
x_axis_range: tuple[float, float],
y_axis_range: tuple[float, float],
levels: int | ArrayLike | Inherit = INHERIT,
color_map: str | Colormap | Inherit = INHERIT,
color_map_range: Optional[tuple[float, float]] = None,
show_color_bar: bool | Inherit = INHERIT,
filled: bool | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
line_widths: float | ArrayLike | Inherit = INHERIT,
number_of_points: tuple[int, int] = (500, 500),
) -> Self:
"""
Creates a Contour object from a function.
Parameters
----------
func : Callable[[ArrayLike, ArrayLike], ArrayLike]
Function to be plotted. Works with regular functions and lambda functions.
x_axis_range, y_axis_range : tuple[float, float], optional
The range of x and y values used for the axes as tuples containing the start and end of the range.
levels : int | ArrayLike
If `levels` is an integer, it defines the number of levels to use in the contour.
If `levels` is an array, it defines the value of each contour level.
Typical range is ``5`` to ``20`` when given as an integer.
Default depends on the ``figure_style`` configuration.
color_map : str or Colormap
The color map to use for the :class:`~graphinglib.data_plotting_2d.Contour`. Can either be specified as a
string (named colormap from Matplotlib) or a Colormap object.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
color_map_range: tuple[float, float], optional
The data range covered by the color map, given as ``(minimum, maximum)``.
show_color_bar : bool
Whether or not to display the color bar next to the plot.
Default depends on the ``figure_style`` configuration.
filled : bool
Wheter or not to fill the contour with color.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the filled contour.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
line_widths : float | ArrayLike
If the contour is not filled, the width of the contour lines. If an array is provided, it defines the line
width for each contour level.
Typical range is ``0.5`` to ``3`` points.
Default depends on the ``figure_style`` configuration.
number_of_points : tuple[int, int]
Number of points in the x and y coordinates.
Defaults to ``(50, 50)``.
Returns
-------
A :class:`~graphinglib.data_plotting_2d.Contour` object from a function.
"""
x = np.linspace(x_axis_range[0], x_axis_range[1], number_of_points[0])
y = np.linspace(y_axis_range[0], y_axis_range[1], number_of_points[1])
x_mesh, y_mesh = np.meshgrid(x, y)
z_data = func(x_mesh, y_mesh)
return cls(
z_data,
x_mesh,
y_mesh,
levels,
color_map,
color_map_range,
show_color_bar,
filled,
alpha,
line_widths,
)
@property
def x_mesh(self) -> ArrayLike:
return self._x_mesh
@x_mesh.setter
def x_mesh(self, x_mesh: ArrayLike) -> None:
self._x_mesh = None if x_mesh is None else np.asarray(x_mesh)
@property
def y_mesh(self) -> ArrayLike:
return self._y_mesh
@y_mesh.setter
def y_mesh(self, y_mesh: ArrayLike) -> None:
self._y_mesh = None if y_mesh is None else np.asarray(y_mesh)
@property
def z_data(self) -> ArrayLike:
return self._z_data
@z_data.setter
def z_data(self, z_data: ArrayLike) -> None:
self._z_data = np.asarray(z_data)
@property
def levels(self) -> int | ArrayLike | Inherit:
return self._levels
@levels.setter
def levels(self, levels: int | ArrayLike | Inherit) -> None:
self._levels = levels
@property
def color_map(self) -> str | Colormap:
return self._color_map
@color_map.setter
def color_map(self, color_map: str | Colormap) -> None:
self._color_map = color_map
@property
def color_map_range(self) -> tuple[float, float]:
return self._color_map_range
@color_map_range.setter
def color_map_range(self, color_map_range: tuple[float, float]) -> None:
self._color_map_range = color_map_range
@property
def show_color_bar(self) -> bool:
return self._show_color_bar
@show_color_bar.setter
def show_color_bar(self, show_color_bar: bool) -> None:
self._show_color_bar = show_color_bar
@property
def filled(self) -> bool:
return self._filled
@filled.setter
def filled(self, filled: bool) -> None:
self._filled = filled
@property
def alpha(self) -> float:
return self._alpha
@alpha.setter
def alpha(self, alpha: float) -> None:
self._alpha = alpha
@property
def line_widths(self) -> float | ArrayLike:
return self._line_widths
@line_widths.setter
def line_widths(self, line_widths: float | ArrayLike) -> None:
self._line_widths = line_widths
@property
def color_bar_params(self) -> dict:
return self._color_bar_params
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.data_plotting_2d.Contour`.
"""
return deepcopy(self)
[docs]
def set_color_bar_params(
self,
label: Optional[str] = None,
position: Optional[str] = None,
**color_bar_params,
) -> None:
"""
Sets the color bar parameters.
Parameters
----------
label : str, optional
Label of the color bar.
position : str, optional
Position of the color bar relative to the ``Figure``. It can be "left",
"right", "top" or "bottom". This also determines the orientation of the
color bar (vertical if the color bar is plotted on the "left" or "right",
horizontal otherwise). If None, the color bar is plotted on the right
side of the ``Figure``.
Values are ``"left"``, ``"right"``, ``"top"``, and ``"bottom"``.
**color_bar_params:
Additional keyword arguments are passed to ``plt.colorbar`` call.
"""
self._color_bar_params = color_bar_params
if label is not None:
self._color_bar_params["label"] = label
if position is not None:
self._color_bar_params["location"] = position
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None:
"""
Plots the element in the specified
`Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_.
"""
if self._x_mesh is None or self._y_mesh is None:
x_mesh, y_mesh = np.meshgrid(
np.arange(self._z_data.shape[1]),
np.arange(self._z_data.shape[0]),
)
else:
x_mesh = self._x_mesh
y_mesh = self._y_mesh
params = {
"levels": self._levels,
"cmap": self._color_map,
"alpha": self._alpha,
"linewidths": self._line_widths if not self._filled else None,
}
if self._color_map_range is not None:
params["vmin"] = min(self._color_map_range)
params["vmax"] = max(self._color_map_range)
params = {
k: v for k, v in params.items() if not isinstance(v, str) or v != INHERIT
}
if self._filled:
cont = axes.contourf(
x_mesh,
y_mesh,
self._z_data,
zorder=z_order,
**params,
)
else:
cont = axes.contour(
x_mesh,
y_mesh,
self._z_data,
zorder=z_order,
**params,
)
if self._show_color_bar:
fig = axes.get_figure()
fig.colorbar(cont, ax=axes, **self._color_bar_params)
[docs]
@dataclass
class Stream(Plottable2D):
"""
This class implements stream plots.
Parameters
----------
x_data, y_data : ArrayLike
x and y coordinates of the vectors as a mesh grid.
u_data, v_data : ArrayLike
Magnitudes of the vectors for each point of the mesh grid.
density : float or tuple[float, float]
Density of stream lines. Can be specified independently for the x and y coordinates
by specifying a density tuple instead. Defaults to 1.
Typical range is ``0.5`` to ``3``.
line_width : float
Width of the stream lines. Default depends on the ``figure_style`` configuration.
Typical range is ``0.5`` to ``3`` points.
color : str or ArrayLike
Color of the stream lines. If an array of intensities is provided, the values are mapped to the specified color map.
Default depends on the ``figure_style`` configuration.
color_map : str or Colormap
Color map of the stream lines, to be used in combination with the color parameter to specify intensity.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
arrow_size : float
Arrow size multiplier. Default depends on the ``figure_style`` configuration.
Typical range is ``0.5`` to ``3``.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``). They may also be arrays of
intensity values, which are mapped through ``color_map``.
"""
[docs]
def __init__(
self,
x_data: ArrayLike,
y_data: ArrayLike,
u_data: ArrayLike,
v_data: ArrayLike,
density: float | tuple[float, float] = 1,
line_width: float | Inherit = INHERIT,
color: str | ArrayLike | Inherit = INHERIT,
color_map: str | Colormap | Inherit = INHERIT,
arrow_size: float | Inherit = INHERIT,
) -> None:
"""
This class implements stream plots.
Parameters
----------
x_data, y_data : ArrayLike
x and y coordinates of the vectors as a mesh grid.
u_data, v_data : ArrayLike
Magnitudes of the vectors for each point of the mesh grid.
density : float or tuple[float, float]
Density of stream lines. Can be specified independently for the x and y coordinates
by specifying a density tuple instead. Defaults to 1.
Typical range is ``0.5`` to ``3``.
line_width : float
Width of the stream lines. Default depends on the ``figure_style`` configuration.
Typical range is ``0.5`` to ``3`` points.
color : str or ArrayLike
Color of the stream lines. If an array of intensities is provided, the values are mapped to the specified color map.
Default depends on the ``figure_style`` configuration.
color_map : str or Colormap
Color map of the stream lines, to be used in combination with the color parameter to specify intensity.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
Default depends on the ``figure_style`` configuration.
arrow_size : float
Arrow size multiplier. Default depends on the ``figure_style`` configuration.
Typical range is ``0.5`` to ``3``.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``). They may also be arrays of
intensity values, which are mapped through ``color_map``.
"""
self._x_data = np.asarray(x_data)
self._y_data = np.asarray(y_data)
self._u_data = np.asarray(u_data)
self._v_data = np.asarray(v_data)
self._density = density
self._line_width = line_width
self._color = color
self._color_map = color_map
self._arrow_size = arrow_size
[docs]
@classmethod
def from_function(
cls,
func: Callable[[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]],
x_axis_range: tuple[float, float],
y_axis_range: tuple[float, float],
number_of_points_x: int = 30,
number_of_points_y: int = 30,
density: float | tuple[float, float] = 1,
line_width: float | Inherit = INHERIT,
color: str | Inherit = INHERIT,
color_map: str | Colormap | Inherit = INHERIT,
arrow_size: float | Inherit = INHERIT,
) -> Self:
"""
Creates a :class:`~graphinglib.data_plotting_2d.Stream` from a function.
Parameters
----------
func : Callable[[ArrayLike, ArrayLike], [ArrayLike, ArrayLike]]
Function to be plotted. Works with regular functions and lambda functions.
x_axis_range, y_axis_range : tuple[float, float], optional
The range of x and y values used for the axes as tuples containing the start and end of the range.
number_of_points_x, number_of_points_y : int
Number of points to fill the x and y ranges.
Defaults to 30.
density : float or tuple[float, float]
Density of stream lines. Can be specified independently for the x and y coordinates
by specifying a density tuple instead. Defaults to 1.
Typical range is ``0.5`` to ``3``.
line_width : float
Width of the stream lines. Default depends on the ``figure_style`` configuration.
Typical range is ``0.5`` to ``3`` points.
color : str
Color of the stream lines. Default depends on the ``figure_style`` configuration.
color_map : str or Colormap
Color map of the stream lines. Default depends on the ``figure_style`` configuration.
Examples include ``"viridis"``, ``"plasma"``, and ``"coolwarm"``.
arrow_size : float
Arrow size multiplier. Default depends on the ``figure_style`` configuration.
Typical range is ``0.5`` to ``3``.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``). They may also be arrays of
intensity values, which are mapped through ``color_map``.
Returns
-------
A :class:`~graphinglib.data_plotting_2d.Stream` object from a function.
"""
x = np.linspace(x_axis_range[0], x_axis_range[1], number_of_points_x)
y = np.linspace(y_axis_range[0], y_axis_range[1], number_of_points_y)
x_grid, y_grid = np.meshgrid(x, y)
u, v = func(x_grid, y_grid)
return cls(x, y, u, v, density, line_width, color, color_map, arrow_size)
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.data_plotting_2d.Stream`.
"""
return deepcopy(self)
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None:
"""
Plots the element in the specified Axes.
"""
params = {
"density": self._density,
"linewidth": self._line_width,
"cmap": self._color_map,
"arrowsize": self._arrow_size,
}
params = {k: v for k, v in params.items() if v != INHERIT}
if is_inherit(self._color):
pass
else:
params["color"] = self._color
axes.streamplot(
x=self._x_data,
y=self._y_data,
u=self._u_data,
v=self._v_data,
zorder=z_order,
**params,
)