from __future__ import annotations
from .inherit import INHERIT, Inherit
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Literal, Optional, Protocol, runtime_checkable
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
from matplotlib.figure import Figure as MPLFigure
from numpy.typing import ArrayLike
from .legend_artists import VerticalLineCollection
from .tools import _copy_with_overrides
try:
from typing import Self
except ImportError:
from typing_extensions import Self
@runtime_checkable
class Plottable(Protocol):
"""
Dummy class for a general plottable object.
.. attention:: Not to be used directly.
"""
def copy_with(self, **kwargs) -> Self:
"""
Returns a deep copy of the Plottable with specified attributes overridden. This is useful when multiple
properties need to be changed in copies of Plottable objects, as it allows to modify the attributes in a single
call.
Parameters
----------
**kwargs
Public writable properties to override in the copied Plottable. The keys should be property names to
modify and the values are the new values for those properties.
Returns
-------
Self
A new instance with the specified attributes overridden.
Examples
--------
Copy an existing Curve and change the color and line_style at the same time::
curve = Curve(x_data, y_data, color='blue')
new_curve = curve.copy_with(color='red', line_style='dashed')
"""
return _copy_with_overrides(self, **kwargs)
def __deepcopy__(self, memo: dict) -> Self:
"""
Creates a deep copy of the Plottable instance, intentionally excluding the 'handle' attribute from the copy.
This avoids issues when copying a Plottable that has been previously drawn and stored.
"""
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
excluded_attrs = ["handle"]
for property_, value in self.__dict__.items():
if property_ not in excluded_attrs:
result.__dict__[property_] = deepcopy(value, memo)
for attr in excluded_attrs:
if hasattr(self, attr):
setattr(result, attr, None)
return result
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>`_.
"""
pass
class GraphingException(Exception):
"""
General exception raised for the GraphingLib modules.
"""
pass
[docs]
class Hlines(Plottable):
"""
This class implements simple horizontal lines.
Parameters
----------
y : ArrayLike
Vertical positions at which the lines should be plotted.
x_min : ArrayLike, optional
Horizontal start position of the lines. Each lines can have a different start.
If not specified, lines will span the entire axes. Defaults to ``None``.
x_max : ArrayLike, optional
Horizontal end position of the lines. Each lines can habe a different end.
If not specified, lines will span the entire axes. Defaults to ``None``.
label : str, optional
Label to be displayed in the legend.
colors : list[str]
Colors to use for the lines. One color for every line or a color
per line can be specified.
Default depends on the ``figure_style`` configuration.
line_widths : list[float]
Line widths to use for the lines. One width for every line or a width
per line can be specified.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_styles : list[str]
Line styles to use for the lines. One style for every line or a style
per line can be specified.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the lines.
Range is ``0`` (transparent) to ``1`` (opaque).
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,
y: ArrayLike,
x_min: Optional[ArrayLike] = None,
x_max: Optional[ArrayLike] = None,
label: Optional[str] = None,
colors: list[str] | str | Inherit = INHERIT,
line_widths: list[float] | float | Inherit = INHERIT,
line_styles: list[str] | str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
) -> None:
self._in_init = True
self._y = None
self._x_min = None
self._x_max = None
self._colors = INHERIT
self._line_widths = INHERIT
self._line_styles = INHERIT
self.y = y
self.x_min = x_min
self.x_max = x_max
self.label = label
self.colors = colors
self.line_widths = line_widths
self.line_styles = line_styles
self._alpha = alpha
self._in_init = False
self._validate_state()
def _validate_state(self) -> None:
if (self._x_min is None) ^ (self._x_max is None):
raise GraphingException(
"Either both x_min and x_max are specified or none of them"
)
if isinstance(self._y, (int, float)) and isinstance(
self._colors, (list, np.ndarray)
):
if len(self._colors) > 1:
raise GraphingException(
"There can't be multiple colors for a single line!"
)
if isinstance(self._y, (int, float)) and isinstance(
self._line_styles, (list, np.ndarray)
):
if len(self._line_styles) > 1:
raise GraphingException(
"There can't be multiple line styles for a single line!"
)
if isinstance(self._y, (int, float)) and isinstance(
self._line_widths, (list, np.ndarray)
):
if len(self._line_widths) > 1:
raise GraphingException(
"There can't be multiple line widths for a single line!"
)
if isinstance(self._y, (list, np.ndarray)):
if isinstance(self._colors, list) and len(self._y) != len(self._colors):
raise GraphingException(
"There must be the same number of colors and lines!"
)
if isinstance(self._line_styles, list) and len(self._y) != len(
self._line_styles
):
raise GraphingException(
"There must be the same number of line styles and lines!"
)
if isinstance(self._line_widths, list) and len(self._y) != len(
self._line_widths
):
raise GraphingException(
"There must be the same number of line widths and lines!"
)
@property
def y(self) -> ArrayLike:
return self._y
@y.setter
def y(self, y: ArrayLike) -> None:
if isinstance(y, (list, np.ndarray)):
self._y = np.asarray(y)
else:
self._y = y
if not self._in_init:
self._validate_state()
@property
def x_min(self) -> ArrayLike | None:
return self._x_min
@x_min.setter
def x_min(self, x_min: Optional[ArrayLike]) -> None:
if isinstance(x_min, (list, np.ndarray)):
self._x_min = np.asarray(x_min)
else:
self._x_min = x_min
if not self._in_init:
self._validate_state()
@property
def x_max(self) -> ArrayLike | None:
return self._x_max
@x_max.setter
def x_max(self, x_max: Optional[ArrayLike]) -> None:
if isinstance(x_max, (list, np.ndarray)):
self._x_max = np.asarray(x_max)
else:
self._x_max = x_max
if not self._in_init:
self._validate_state()
@property
def label(self) -> Optional[str]:
return self._label
@label.setter
def label(self, label: Optional[str]) -> None:
self._label = label
@property
def colors(self) -> list[str] | str:
return self._colors
@colors.setter
def colors(self, colors: list[str] | str) -> None:
self._colors = colors
if not self._in_init:
self._validate_state()
@property
def line_widths(self) -> list[float] | float:
return self._line_widths
@line_widths.setter
def line_widths(self, line_widths: list[float] | float) -> None:
self._line_widths = line_widths
if not self._in_init:
self._validate_state()
@property
def line_styles(self) -> list[str] | str:
return self._line_styles
@line_styles.setter
def line_styles(self, line_styles: list[str] | str) -> None:
self._line_styles = line_styles
if not self._in_init:
self._validate_state()
@property
def alpha(self) -> float | Inherit:
return self._alpha
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.graph_elements.Hlines` object.
"""
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 isinstance(self._y, (list, np.ndarray)) and len(self._y) > 1:
if self._x_max is not None and self._x_min is not None:
params = {
"colors": self._colors,
"linestyles": self._line_styles,
"linewidths": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.hlines(
self._y,
self._x_min,
self._x_max,
zorder=z_order,
**params,
)
params.pop("linewidths")
else:
params = {
"color": self._colors,
"linestyle": self._line_styles,
"linewidth": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
for i in range(len(self._y)):
axes.axhline(
self._y[i],
zorder=z_order,
**{
k: v if isinstance(v, (int, float, str)) else v[i]
for k, v in params.items()
},
)
params.pop("linewidth")
self.handle = LineCollection(
[[(0, 0)]] * (len(self._y) if len(self._y) <= 3 else 3),
**params,
)
else:
if self._x_max is not None and self._x_min is not None:
params = {
"colors": self._colors,
"linestyles": self._line_styles,
"linewidths": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.hlines(
self._y,
self._x_min,
self._x_max,
zorder=z_order,
**params,
)
params.pop("linewidths")
else:
params = {
"color": self._colors,
"linestyle": self._line_styles,
"linewidth": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.axhline(self._y, zorder=z_order, **params)
params.pop("linewidth")
if isinstance(self._y, (int, float)):
self.handle = LineCollection([[(0, 0)]] * 1, **params)
else:
self.handle = LineCollection(
[[(0, 0)]] * (len(self._y) if len(self._y) <= 3 else 3),
**params,
)
[docs]
class Vlines(Plottable):
"""
This class implements simple vertical lines.
Parameters
----------
x : ArrayLike
Horizontal positions at which the lines should be plotted.
y_min : ArrayLike, optional
Vertical start position of the lines. Each line can have a different start.
If not specified, lines will span the entire axes. Defaults to ``None``.
y_max : ArrayLike, optional
Vertical end position of the lines. Each line can habe a different end.
If not specified, lines will span the entire axes. Defaults to ``None``.
label : str, optional
Label to be displayed in the legend.
colors : list[str]
Colors to use for the lines. One color for every line or a color
per line can be specified.
Default depends on the ``figure_style`` configuration.
line_widths : list[float]
Line widths to use for the lines. One width for every line or a width
per line can be specified.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_styles : list[str]
Line styles to use for the lines. One style for every line or a style
per line can be specified.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the lines.
Range is ``0`` (transparent) to ``1`` (opaque).
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: ArrayLike,
y_min: Optional[ArrayLike] = None,
y_max: Optional[ArrayLike] = None,
label: Optional[str] = None,
colors: list[str] | str | Inherit = INHERIT,
line_widths: list[float] | float | Inherit = INHERIT,
line_styles: list[str] | str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
) -> None:
self._in_init = True
self._x = None
self._y_min = None
self._y_max = None
self._colors = INHERIT
self._line_styles = INHERIT
self._line_widths = INHERIT
self.x = x
self.y_min = y_min
self.y_max = y_max
self.label = label
self.colors = colors
self.line_styles = line_styles
self.line_widths = line_widths
self._alpha = alpha
self._in_init = False
self._validate_state()
def _validate_state(self) -> None:
if isinstance(self._x, (int, float)) and isinstance(
self._colors, (list, np.ndarray)
):
if len(self._colors) > 1:
raise GraphingException(
"There can't be multiple colors for a single line!"
)
if isinstance(self._x, (int, float)) and isinstance(
self._line_styles, (list, np.ndarray)
):
if len(self._line_styles) > 1:
raise GraphingException(
"There can't be multiple line styles for a single line!"
)
if isinstance(self._x, (int, float)) and isinstance(
self._line_widths, (list, np.ndarray)
):
if len(self._line_widths) > 1:
raise GraphingException(
"There can't be multiple line widths for a single line!"
)
if isinstance(self._x, (list, np.ndarray)):
if isinstance(self._colors, list) and len(self._x) != len(self._colors):
raise GraphingException(
"There must be the same number of colors and lines!"
)
if isinstance(self._line_styles, list) and len(self._x) != len(
self._line_styles
):
raise GraphingException(
"There must be the same number of line styles and lines!"
)
if isinstance(self._line_widths, list) and len(self._x) != len(
self._line_widths
):
raise GraphingException(
"There must be the same number of line widths and lines!"
)
@property
def x(self) -> ArrayLike:
return self._x
@x.setter
def x(self, x: ArrayLike) -> None:
if isinstance(x, (list, np.ndarray)):
self._x = np.asarray(x)
else:
self._x = x
if not self._in_init:
self._validate_state()
@property
def y_min(self) -> ArrayLike | None:
return self._y_min
@y_min.setter
def y_min(self, y_min: Optional[ArrayLike]) -> None:
if isinstance(y_min, (list, np.ndarray)):
self._y_min = np.asarray(y_min)
else:
self._y_min = y_min
@property
def y_max(self) -> ArrayLike | None:
return self._y_max
@y_max.setter
def y_max(self, y_max: Optional[ArrayLike]) -> None:
if isinstance(y_max, (list, np.ndarray)):
self._y_max = np.asarray(y_max)
else:
self._y_max = y_max
@property
def label(self) -> Optional[str]:
return self._label
@label.setter
def label(self, label: Optional[str]) -> None:
self._label = label
@property
def colors(self) -> list[str] | str:
return self._colors
@colors.setter
def colors(self, colors: list[str] | str) -> None:
self._colors = colors
if not self._in_init:
self._validate_state()
@property
def line_widths(self) -> list[float] | float:
return self._line_widths
@line_widths.setter
def line_widths(self, line_widths: list[float] | float) -> None:
self._line_widths = line_widths
if not self._in_init:
self._validate_state()
@property
def line_styles(self) -> list[str] | str:
return self._line_styles
@line_styles.setter
def line_styles(self, line_styles: list[str] | str) -> None:
self._line_styles = line_styles
if not self._in_init:
self._validate_state()
@property
def alpha(self) -> float | Inherit:
return self._alpha
@alpha.setter
def alpha(self, alpha: float | Inherit) -> None:
self._alpha = alpha
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.graph_elements.Vlines` object.
"""
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 isinstance(self._x, (list, np.ndarray)) and len(self._x) > 1:
if self._y_min is not None and self._y_max is not None:
params = {
"colors": self._colors,
"linestyles": self._line_styles,
"linewidths": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.vlines(
self._x,
self._y_min,
self._y_max,
zorder=z_order,
**params,
)
params.pop("linewidths")
else:
params = {
"color": self._colors,
"linestyle": self._line_styles,
"linewidth": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
for i in range(len(self._x)):
axes.axvline(
self._x[i],
zorder=z_order,
**{
k: v if isinstance(v, (int, float, str)) else v[i]
for k, v in params.items()
},
)
params.pop("linewidth")
self.handle = VerticalLineCollection(
[[(0, 0)]] * (len(self._x) if len(self._x) <= 4 else 4),
**params,
)
else:
if self._y_min is not None and self._y_max is not None:
params = {
"colors": self._colors,
"linestyles": self._line_styles,
"linewidths": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.vlines(
self._x,
self._y_min,
self._y_max,
zorder=z_order,
**params,
)
params.pop("linewidths")
else:
params = {
"color": self._colors,
"linestyle": self._line_styles,
"linewidth": self._line_widths,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.axvline(self._x, zorder=z_order, **params)
params.pop("linewidth")
if isinstance(self._x, (int, float)):
self.handle = VerticalLineCollection([[(0, 0)]] * 1, **params)
else:
self.handle = VerticalLineCollection(
[[(0, 0)]] * (len(self._x) if len(self._x) <= 4 else 4),
**params,
)
[docs]
class Point(Plottable):
"""
This class implements a point object.
The :class:`~graphinglib.graph_elements.Point`
object can be used to show important coordinates in a plot
or add a label to some point.
Parameters
----------
x, y : float
The x and y coordinates of the :class:`~graphinglib.graph_elements.Point`.
label : str, optional
Label to be attached to the :class:`~graphinglib.graph_elements.Point`.
face_color : str or None
Face color of the marker.
Default depends on the ``figure_style`` configuration.
edge_color : str or None
Edge color of the marker.
Default depends on the ``figure_style`` configuration.
marker_size : float
Size of the marker.
Typical range is ``10`` to ``100``.
Default depends on the ``figure_style`` configuration.
marker_style : str
Style of the marker.
Values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``,
``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, and ``"_"``.
Default depends on the ``figure_style`` configuration.
edge_width : float
Edge width of the marker.
Typical range is ``0`` to ``3``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the point.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
font_size : float
Font size for the text attached to the marker.
Typical range is ``8`` to ``20``.
Default depends on the ``figure_style`` configuration.
text_color : str
Color of the text attached to the marker.
"same as point" uses the color of the point (prioritize edge color, then face color). Default depends on the ``figure_style`` configuration.
h_align, v_align : str
Horizontal and vertical alignment of the text attached
to the :class:`~graphinglib.graph_elements.Point`.
Horizontal alignment values include ``"left"``, ``"center"``, and ``"right"``. Vertical alignment values
include ``"bottom"``, ``"baseline"``, ``"center"``, ``"center_baseline"``, and ``"top"``.
Defaults to bottom left.
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: float,
y: float,
label: Optional[str] = None,
face_color: Optional[str] | Inherit = INHERIT,
edge_color: Optional[str] | Inherit = INHERIT,
marker_size: float | Inherit = INHERIT,
marker_style: str | Inherit = INHERIT,
edge_width: float | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
font_size: int | Literal["same as figure"] = "same as figure",
text_color: str | Inherit = INHERIT,
h_align: str = "left",
v_align: str = "bottom",
) -> None:
"""
This class implements a point object.
The point object can be used to show important coordinates in a plot
or add a label to some point.
Parameters
----------
x, y : float
The x and y coordinates of the :class:`~graphinglib.graph_elements.Point`.
label : str, optional
Label to be attached to the :class:`~graphinglib.graph_elements.Point`.
face_color : str or None
Face color of the marker.
Default depends on the ``figure_style`` configuration.
edge_color : str or None
Edge color of the marker.
Default depends on the ``figure_style`` configuration.
marker_size : float
Size of the marker.
Typical range is ``10`` to ``100``.
Default depends on the ``figure_style`` configuration.
marker_style : str
Style of the marker.
Values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``,
``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, and ``"_"``.
Default depends on the ``figure_style`` configuration.
edge_width : float
Edge width of the marker.
Typical range is ``0`` to ``3``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the point.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
font_size : float
Font size for the text attached to the marker.
Typical range is ``8`` to ``20``.
Default depends on the ``figure_style`` configuration.
text_color : str
Color of the text attached to the marker.
"same as point" uses the color of the point (prioritize edge color, then face color). Default depends on the ``figure_style`` configuration.
h_align, v_align : str
Horizontal and vertical alignment of the text attached
to the :class:`~graphinglib.graph_elements.Point`.
Horizontal alignment values include ``"left"``, ``"center"``, and ``"right"``. Vertical alignment values
include ``"bottom"``, ``"baseline"``, ``"center"``, ``"center_baseline"``, and ``"top"``.
Defaults to bottom left.
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 = x
self.y = y
self.label = label
self.face_color = face_color
self.edge_color = edge_color
self.marker_size = marker_size
self.marker_style = marker_style
self.edge_width = edge_width
self.alpha = alpha
self.font_size = font_size
self.text_color = text_color
self.h_align = h_align
self.v_align = v_align
self._show_coordinates: bool = False
@staticmethod
def _validate_coordinate(value: float) -> None:
if not isinstance(value, (int, float)) or isinstance(value, bool):
raise GraphingException(
"The x and y coordinates for a point must be a single number each!"
)
@property
def x(self) -> float:
return self._x
@x.setter
def x(self, x: float) -> None:
self._validate_coordinate(x)
self._x = x
@property
def y(self) -> float:
return self._y
@y.setter
def y(self, y: float) -> None:
self._validate_coordinate(y)
self._y = y
@property
def label(self) -> Optional[str]:
return self._label
@label.setter
def label(self, label: Optional[str]) -> None:
self._label = label
@property
def face_color(self) -> str | None:
return self._face_color
@face_color.setter
def face_color(self, face_color: str) -> None:
self._face_color = face_color
@property
def edge_color(self) -> str | None:
return self._edge_color
@edge_color.setter
def edge_color(self, edge_color: str) -> None:
self._edge_color = edge_color
@property
def marker_size(self) -> float | Inherit:
return self._marker_size
@marker_size.setter
def marker_size(self, marker_size: float | Inherit) -> None:
self._marker_size = marker_size
@property
def marker_style(self) -> str:
return self._marker_style
@marker_style.setter
def marker_style(self, marker_style: str) -> None:
self._marker_style = marker_style
@property
def edge_width(self) -> float | Inherit:
return self._edge_width
@edge_width.setter
def edge_width(self, edge_width: float | Inherit) -> None:
self._edge_width = edge_width
@property
def alpha(self) -> float | Inherit:
return self._alpha
@alpha.setter
def alpha(self, alpha: float | Inherit) -> None:
self._alpha = alpha
@property
def font_size(self) -> float | Literal["same as figure"]:
return self._font_size
@font_size.setter
def font_size(self, font_size: float | Literal["same as figure"]) -> None:
self._font_size = font_size
@property
def text_color(self) -> str:
return self._text_color
@text_color.setter
def text_color(self, text_color: str) -> None:
self._text_color = text_color
@property
def h_align(self) -> str:
return self._h_align
@h_align.setter
def h_align(self, h_align: str) -> None:
self._h_align = h_align
@property
def v_align(self) -> str:
return self._v_align
@v_align.setter
def v_align(self, v_align: str) -> None:
self._v_align = v_align
@property
def show_coordinates(self) -> bool:
return self._show_coordinates
@show_coordinates.setter
def show_coordinates(self, show_coordinates: bool) -> None:
self._show_coordinates = show_coordinates
@property
def coordinates(self) -> tuple[float, float]:
return (self._x, self._y)
@coordinates.setter
def coordinates(self, coordinates: tuple[float, float]) -> None:
x, y = coordinates
self.x = x
self.y = y
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.graph_elements.Point` object.
"""
return deepcopy(self)
[docs]
def add_coordinates(self) -> None:
"""
Displays the coordinates of the :class:`~graphinglib.graph_elements.Point` next to it.
"""
self._show_coordinates = True
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._face_color is None and self._edge_color is None:
raise GraphingException(
"Both the face color and edge color of the point can't be None. Set at least one of them."
)
size = self._font_size if self._font_size != "same as figure" else None
prefix = " " if self._h_align == "left" else ""
postfix = " " if self._h_align == "right" else ""
if self._label is not None and not self._show_coordinates:
point_label = prefix + self._label + postfix
else:
point_label = None
params = {
"c": self._face_color if self._face_color is not None else "none",
"edgecolors": self._edge_color if self._edge_color is not None else "none",
"s": self._marker_size,
"marker": self._marker_style,
"linewidths": self._edge_width,
"alpha": self._alpha,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.scatter(
self._x,
self._y,
zorder=z_order,
**params,
)
# get text color. if _text_color is "same as point", use the color of the point (prioritize edge color, then face color)
if self._text_color == "same as point":
if self._edge_color is not None:
text_color = self._edge_color
else:
text_color = self._face_color
else:
text_color = self._text_color
params = {
"color": text_color,
"fontsize": size,
"horizontalalignment": self._h_align,
"verticalalignment": self._v_align,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.annotate(
point_label,
(self._x, self._y),
zorder=z_order,
**params,
)
if self._show_coordinates:
prefix = " " if self._h_align == "left" else ""
postfix = " " if self._h_align == "right" else ""
if self._label is not None:
point_label = (
prefix
+ self._label
+ " : "
+ f"({self._x:.3f}, {self._y:.3f})"
+ postfix
)
else:
point_label = prefix + f"({self._x:.3f}, {self._y:.3f})" + postfix
if self._text_color == "same as point":
if self._edge_color is not None:
text_color = self._edge_color
else:
text_color = self._face_color
else:
text_color = self._text_color
params = {
"color": text_color,
"fontsize": size,
"horizontalalignment": self._h_align,
"verticalalignment": self._v_align,
}
params = {k: v for k, v in params.items() if v != INHERIT}
axes.annotate(
point_label,
(self._x, self._y),
zorder=z_order,
**params,
)
[docs]
@dataclass
class Text(Plottable):
"""
This class allows text to be plotted.
It is also possible to attach an arrow to the :class:`~graphinglib.graph_elements.Text`
with the method :py:meth:`~graphinglib.graph_elements.Text.attach_arrow`
to point at something of interest in the plot.
Parameters
----------
x, y : float
The x and y coordinates at which to plot the :class:`~graphinglib.graph_elements.Text`.
text : str
The text to be plotted.
color : str
Color of the text.
Default depends on the ``figure_style`` configuration.
font_size : float
Font size of the text.
Typical range is ``8`` to ``20``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the text.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
h_align, v_align : str
Horizontal and vertical alignment of the text.
Horizontal alignment values include ``"left"``, ``"center"``, and ``"right"``. Vertical alignment values
include ``"bottom"``, ``"baseline"``, ``"center"``, ``"center_baseline"``, and ``"top"``.
Default depends on the ``figure_style`` configuration.
rotation : float
Rotation angle of the text in degrees.
Defaults to 0.
highlight_color : str, optional
Color of the background highlight box behind the text.
If specified, a box will be drawn behind the text.
Default is ``None`` (no highlight).
highlight_alpha : float, optional
Opacity of the highlight box.
Range is ``0`` (transparent) to ``1`` (opaque).
Defaults to 1.0.
highlight_padding : float, optional
Padding around the text for the highlight box. A value of 0 means no padding.
Defaults to 0.1.
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)``).
"""
_x: float
_y: float
_text: str
_color: str | Inherit = INHERIT
_font_size: float | Literal["same as figure"] = "same as figure"
_alpha: float | Inherit = INHERIT
_h_align: str | Inherit = INHERIT
_v_align: str | Inherit = INHERIT
_rotation: float = 0.0
_highlight_color: Optional[str] = None
_highlight_alpha: float = 1.0
_highlight_padding: float = 0.1
_arrow_pointing_to: Optional[tuple[float]] = field(default=None, init=False)
[docs]
def __init__(
self,
x: float,
y: float,
text: str,
color: str | Inherit = INHERIT,
font_size: float | Literal["same as figure"] = "same as figure",
alpha: float | Inherit = INHERIT,
h_align: str | Inherit = INHERIT,
v_align: str | Inherit = INHERIT,
rotation: float = 0.0,
highlight_color: Optional[str] = None,
highlight_alpha: float = 1.0,
highlight_padding: float = 0.1,
) -> None:
"""
This class allows text to be plotted.
It is also possible to attach an arrow to the :class:`~graphinglib.graph_elements.Text`
with the method :py:meth:`~graphinglib.graph_elements.Text.attach_arrow`
to point at something of interest in the plot.
Parameters
----------
x, y : float
The x and y coordinates at which to plot the :class:`~graphinglib.graph_elements.Text`.
text : str
The text to be plotted.
color : str
Color of the text.
Default depends on the ``figure_style`` configuration.
font_size : float
Font size of the text.
Typical range is ``8`` to ``20``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the text.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
h_align, v_align : str
Horizontal and vertical alignment of the text.
Horizontal alignment values include ``"left"``, ``"center"``, and ``"right"``. Vertical alignment values
include ``"bottom"``, ``"baseline"``, ``"center"``, ``"center_baseline"``, and ``"top"``.
Default depends on the ``figure_style`` configuration.
rotation : float
Rotation angle of the text in degrees.
Defaults to 0.
highlight_color : str, optional
Color of the background highlight box behind the text.
If specified, a box will be drawn behind the text.
Default is ``None`` (no highlight).
highlight_alpha : float, optional
Opacity of the highlight box.
Range is ``0`` (transparent) to ``1`` (opaque).
Defaults to 1.0.
highlight_padding : float, optional
Padding around the text for the highlight box. A value of 0 means no padding.
Defaults to 0.1.
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 = x
self._y = y
self._text = text
self._color = color
self._font_size = font_size
self._alpha = alpha
self._h_align = h_align
self._v_align = v_align
self._rotation = rotation
self._highlight_color = highlight_color
self._highlight_alpha = highlight_alpha
self._highlight_padding = highlight_padding
self._arrow_pointing_to = None
@property
def x(self) -> float:
return self._x
@x.setter
def x(self, x: float) -> None:
self._x = x
@property
def y(self) -> float:
return self._y
@y.setter
def y(self, y: float) -> None:
self._y = y
@property
def text(self) -> str:
return self._text
@text.setter
def text(self, text: str) -> None:
self._text = text
@property
def color(self) -> str:
return self._color
@color.setter
def color(self, color: str) -> None:
self._color = color
@property
def font_size(self) -> float | Literal["same as figure"]:
return self._font_size
@font_size.setter
def font_size(self, font_size: float | Literal["same as figure"]) -> None:
self._font_size = font_size
@property
def alpha(self) -> float | Inherit:
return self._alpha
@alpha.setter
def alpha(self, alpha: float | Inherit) -> None:
self._alpha = alpha
@property
def h_align(self) -> str:
return self._h_align
@h_align.setter
def h_align(self, h_align: str) -> None:
self._h_align = h_align
@property
def v_align(self) -> str:
return self._v_align
@v_align.setter
def v_align(self, v_align: str) -> None:
self._v_align = v_align
@property
def rotation(self) -> float:
return self._rotation
@rotation.setter
def rotation(self, rotation: float) -> None:
self._rotation = rotation
@property
def highlight_color(self) -> Optional[str]:
return self._highlight_color
@highlight_color.setter
def highlight_color(self, highlight_color: Optional[str]) -> None:
self._highlight_color = highlight_color
@property
def highlight_alpha(self) -> float:
return self._highlight_alpha
@highlight_alpha.setter
def highlight_alpha(self, highlight_alpha: float) -> None:
self._highlight_alpha = highlight_alpha
@property
def highlight_padding(self) -> float:
return self._highlight_padding
@highlight_padding.setter
def highlight_padding(self, highlight_padding: float) -> None:
self._highlight_padding = highlight_padding
@property
def arrow_pointing_to(self) -> Optional[tuple[float]]:
return self._arrow_pointing_to
@arrow_pointing_to.setter
def arrow_pointing_to(self, arrow_pointing_to: Optional[tuple[float]]) -> None:
self._arrow_pointing_to = arrow_pointing_to
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.graph_elements.Text` object.
"""
return deepcopy(self)
[docs]
def add_arrow(
self,
points_to: tuple[float, float],
width: Optional[float] = None,
shrink: Optional[float] = None,
head_width: Optional[float] = None,
head_length: Optional[float] = None,
alpha: Optional[float] = None,
) -> None:
"""
Adds an arrow pointing from the :class:`~graphinglib.graph_elements.Text`
to a specified point.
Parameters
----------
points_to: tuple[float, float]
Coordinates at which to point.
width : float, optional
Arrow width, in points.
Typical range is ``0.5`` to ``3``.
shrink : float, optional
Fraction of the total length of the arrow to shrink from both ends.
Range is ``0`` to ``0.5``. A value of ``0.5`` means the arrow is no longer visible.
head_width : float, optional
Width of the head of the arrow, in points.
Typical range is ``3`` to ``10``.
head_length : float, optional
Length of the head of the arrow, in points.
Typical range is ``3`` to ``10``.
alpha : float, optional
Opacity of the arrow.
Range is ``0`` (transparent) to ``1`` (opaque).
"""
self._arrow_pointing_to = points_to
self._arrow_properties = {}
if width is not None:
self._arrow_properties["width"] = width
if shrink is not None:
self._arrow_properties["shrink"] = shrink
if head_width is not None:
self._arrow_properties["headwidth"] = head_width
if head_length is not None:
self._arrow_properties["headlength"] = head_length
if alpha is not None:
self._arrow_properties["alpha"] = alpha
def _plot_element(
self, target: plt.Axes | MPLFigure, z_order: int, **kwargs
) -> None:
"""
Plots the element in the specified target, which can be either an
`Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_ or a
`Figure <https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html>`.
"""
size = self._font_size if self._font_size != "same as figure" else None
params = {
"color": self._color,
"fontsize": size,
"alpha": self._alpha,
"horizontalalignment": self._h_align,
"verticalalignment": self._v_align,
"rotation": self._rotation,
}
# Add highlight/background box if highlight_color is specified
if self._highlight_color is not None:
bbox_dict = {
"boxstyle": f"square,pad={self._highlight_padding}",
"facecolor": self._highlight_color,
"edgecolor": "none",
"alpha": self._highlight_alpha,
}
params["bbox"] = bbox_dict
params = {k: v for k, v in params.items() if v != INHERIT}
target.text(
self._x,
self._y,
self._text,
zorder=z_order,
**params,
)
if self._arrow_pointing_to is not None and isinstance(target, plt.Axes):
self._arrow_properties["color"] = self._color
params = {
"color": self._color,
"fontsize": size,
"horizontalalignment": self._h_align,
"verticalalignment": self._v_align,
}
params = {k: v for k, v in params.items() if v != INHERIT}
if self._color != INHERIT:
self._arrow_properties["color"] = self._color
params["arrowprops"] = self._arrow_properties
target.annotate(
self._text,
self._arrow_pointing_to,
xytext=(self._x, self._y),
zorder=z_order,
**params,
)
[docs]
@dataclass
class Table(Plottable):
"""
This class allows to plot a table inside a Figure or MultiFigure.
The Table object can be used to add raw data to a figure or add supplementary
information like output parameters for a fit or anyother operation.
Parameters
----------
cell_text : list[str]
Text or data to be displayed in the table. The shape of the provided data
determines the number of columns and rows.
cell_colors : ArrayLike or str, optional
Colors to apply to the cells' background. Must be a list of colors the same
shape as the cells.
Default depends on the ``figure_style`` configuration.
cell_align : str
Alignment of the cells' text. Must be one of the following:
{'left', 'center', 'right'}. Default depends on the ``figure_style`` configuration.
col_labels : list[str], optional
List of labels for the rows of the table. If none are specified, no row labels are displayed.
col_widths : list[float], optional
Widths to apply to the columns. Must be a list the same length as the number of columns.
col_align : str
Alignment of the column labels' text. Must be one of the following:
{'left', 'center', 'right'}. Default depends on the ``figure_style`` configuration.
col_colors : ArrayLike or str, optional
Colors to apply to the column labels' background. Must be a list of colors the same
length as the number of columns.
Default depends on the ``figure_style`` configuration.
row_labels : list[str], optional
List of labels for the rows of the table. If none are specified, no row labels are displayed.
row_align : str
Alignment of the row labels' text. Must be one of the following:
{'left', 'center', 'right'}. Default depends on the ``figure_style`` configuration.
row_colors : ArrayLike or str, optional
Colors to apply to the row labels' background. Must be a list of colors the same
length as the number of rows.
Default depends on the ``figure_style`` configuration.
edge_width : float or str, optional
Width of the table's edges.
Typical range is ``0.5`` to ``3``.
Default depends on the ``figure_style`` configuration.
edge_color : str, optional
Color of the table's edges.
Default depends on the ``figure_style`` configuration.
text_color : str, optional
Color of the text in the table.
Default depends on the ``figure_style`` configuration.
scaling : tuple[float], optional
Horizontal and vertical scaling factors to apply to the table.
Defaults to ``(1, 1.5)``.
location : str
Position of the table inside the axes. Values are ``"best"``, ``"bottom"``, ``"bottom left"``,
``"bottom right"``, ``"center"``, ``"center left"``, ``"center right"``, ``"left"``,
``"lower center"``, ``"lower left"``, ``"lower right"``, ``"right"``, ``"top"``, ``"top left"``,
``"top right"``, ``"upper center"``, ``"upper left"``, and ``"upper right"``.
Defaults to ``"best"``.
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,
cell_text: list[str],
cell_colors: ArrayLike | str | Inherit = INHERIT,
cell_align: str | Inherit = INHERIT,
col_labels: Optional[list[str]] = None,
col_widths: Optional[list[float]] = None,
col_align: str | Inherit = INHERIT,
col_colors: ArrayLike | str | Inherit = INHERIT,
row_labels: Optional[list[str]] = None,
row_align: str | Inherit = INHERIT,
row_colors: ArrayLike | str | Inherit = INHERIT,
edge_width: float | Inherit = INHERIT,
edge_color: str | Inherit = INHERIT,
text_color: str | Inherit = INHERIT,
scaling: tuple[float, float] = (1.0, 1.5),
location: str = "best",
) -> None:
"""
This class allows to plot a table inside a Figure or MultiFigure.
The Table object can be used to add raw data to a figure or add supplementary
information like output parameters for a fit or anyother operation.
Parameters
----------
cell_text : list[str]
Text or data to be displayed in the table. The shape of the provided data
determines the number of columns and rows.
cell_colors : ArrayLike or str, optional
Colors to apply to the cells' background. Must be a list of colors the same
shape as the cells.
Default depends on the ``figure_style`` configuration.
cell_align : str
Alignment of the cells' text. Must be one of the following:
{'left', 'center', 'right'}. Default depends on the ``figure_style`` configuration.
col_labels : list[str], optional
List of labels for the rows of the table. If none are specified, no row labels are displayed.
col_widths : list[float], optional
Widths to apply to the columns. Must be a list the same length as the number of columns.
col_align : str
Alignment of the column labels' text. Must be one of the following:
{'left', 'center', 'right'}. Default depends on the ``figure_style`` configuration.
col_colors : ArrayLike or str, optional
Colors to apply to the column labels' background. Must be a list of colors the same
length as the number of columns.
Default depends on the ``figure_style`` configuration
row_labels : list[str], optional
List of labels for the rows of the table. If none are specified, no row labels are displayed.
row_align : str
Alignment of the row labels' text. Must be one of the following:
{'left', 'center', 'right'}. Default depends on the ``figure_style`` configuration.
row_colors : ArrayLike or str, optional
Colors to apply to the row labels' background. Must be a list of colors the same
length as the number of rows.
Default depends on the ``figure_style`` configuration.
edge_width : float or str, optional
Width of the table's edges.
Typical range is ``0.5`` to ``3``.
Default depends on the ``figure_style`` configuration.
edge_color : str, optional
Color of the table's edges.
Default depends on the ``figure_style`` configuration.
text_color : str, optional
Color of the text within the table.
Default depends on the ``figure_style`` configuration.
scaling : tuple[float], optional
Horizontal and vertical scaling factors to apply to the table.
Defaults to ``(1, 1.5)``.
location : str
Position of the table inside the axes. Values are ``"best"``, ``"bottom"``, ``"bottom left"``,
``"bottom right"``, ``"center"``, ``"center left"``, ``"center right"``, ``"left"``,
``"lower center"``, ``"lower left"``, ``"lower right"``, ``"right"``, ``"top"``, ``"top left"``,
``"top right"``, ``"upper center"``, ``"upper left"``, and ``"upper right"``.
Defaults to ``"best"``.
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._cell_text = cell_text
self._cell_colors = cell_colors
self._cell_align = cell_align
self._col_labels = col_labels
self._col_widths = col_widths
self._col_align = col_align
self._col_colors = col_colors
self._row_labels = row_labels
self._row_align = row_align
self._row_colors = row_colors
self._edge_width = edge_width
self._edge_color = edge_color
self._text_color = text_color
self._scaling = scaling
self._location = location
@property
def cell_text(self) -> list[str]:
return self._cell_text
@cell_text.setter
def cell_text(self, cell_text: list[str]) -> None:
self._cell_text = cell_text
@property
def cell_colors(self) -> ArrayLike | str:
return self._cell_colors
@cell_colors.setter
def cell_colors(self, cell_colors: list) -> None:
self._cell_colors = cell_colors
@property
def cell_align(self) -> str:
return self._cell_align
@cell_align.setter
def cell_align(self, cell_align: str) -> None:
self._cell_align = cell_align
@property
def col_labels(self) -> list[str]:
return self._col_labels
@col_labels.setter
def col_labels(self, col_labels: list[str]) -> None:
self._col_labels = col_labels
@property
def col_widths(self) -> list[float]:
return self._col_widths
@col_widths.setter
def col_widths(self, col_widths: list[float]) -> None:
self._col_widths = col_widths
@property
def col_align(self) -> str:
return self._col_align
@col_align.setter
def col_align(self, col_align: str) -> None:
self._col_align = col_align
@property
def col_colors(self) -> ArrayLike | str:
return self._col_colors
@col_colors.setter
def col_colors(self, col_colors: list) -> None:
self._col_colors = col_colors
@property
def row_labels(self) -> list[str]:
return self._row_labels
@row_labels.setter
def row_labels(self, row_labels: list[str]) -> None:
self._row_labels = row_labels
@property
def row_align(self) -> str:
return self._row_align
@row_align.setter
def row_align(self, row_align: str) -> None:
self._row_align = row_align
@property
def row_colors(self) -> ArrayLike | str:
return self._row_colors
@row_colors.setter
def row_colors(self, row_colors: list) -> None:
self._row_colors = row_colors
@property
def edge_width(self) -> float:
return self._edge_width
@edge_width.setter
def edge_width(self, edge_width: float) -> None:
self._edge_width = edge_width
for (i, j), cell in self.handle.get_celld().items():
cell.set_linewidth(self._edge_width)
@property
def edge_color(self) -> str:
return self._edge_color
@edge_color.setter
def edge_color(self, edge_color: str) -> None:
self._edge_color = edge_color
for (i, j), cell in self.handle.get_celld().items():
cell.set_edgecolor(self._edge_color)
@property
def text_color(self) -> str:
return self._text_color
@text_color.setter
def text_color(self, text_color: str) -> None:
self._text_color = text_color
for (i, j), cell in self.handle.get_celld().items():
cell.set_text_props(color=self._text_color)
@property
def scaling(self) -> tuple[float]:
return self._scaling
@scaling.setter
def scaling(self, scaling: tuple[float]) -> None:
self._scaling = scaling
@property
def location(self) -> str:
return self._location
@location.setter
def location(self, location: str) -> None:
self._location = location
[docs]
def copy(self) -> Self:
"""
Returns a deep copy of the :class:`~graphinglib.graph_elements.Table` object.
"""
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>`_.
"""
params = {
"cellLoc": self._cell_align,
"colLoc": self._col_align,
"rowLoc": self._row_align,
}
params = {k: v for k, v in params.items() if v != INHERIT}
# Set colors to correct shape if they are strings
if isinstance(self._cell_colors, str):
self._cell_colors = [[self._cell_colors] * len(self._cell_text[0])] * len(
self._cell_text
)
if isinstance(self._col_colors, str):
self._col_colors = [self._col_colors] * len(self._cell_text[0])
if isinstance(self._row_colors, str):
self._row_colors = [self._row_colors] * len(self._cell_text)
self.handle = axes.table(
cellText=self._cell_text,
cellColours=self._cell_colors,
colLabels=self._col_labels,
colWidths=self._col_widths,
colColours=self._col_colors,
rowLabels=self._row_labels,
rowColours=self._row_colors,
loc=self._location,
zorder=z_order,
**params,
)
self.handle.auto_set_font_size(False)
self.handle.scale(self._scaling[0], self._scaling[1])
for (i, j), cell in self.handle.get_celld().items():
cell.set_text_props(color=self._text_color)
cell.set_edgecolor(self._edge_color)
cell.set_linewidth(self._edge_width)
[docs]
class PlottableAxMethod(Plottable):
"""
This experimental class allows to call any matplotlib Axes method as a plottable element in a
:class:`~graphinglib.smart_figure.SmartFigure`. This object can be used to create plot types that have not yet been
implemented in GraphingLib.
This class only works with Axes methods that create plottable elements (e.g., ``bar`` or ``pcolormesh``).
Methods that modify axes properties (e.g., ``set_facecolor``, ``set_title``) are not supported.
Parameters
----------
meth : str
Name of the matplotlib Axes method to call. The method will be called as ``axes.meth(*args, **kwargs)``. For
example, this can be "pcolormesh" or "bar".
.. warning::
The provided matplotlib Axes method must accept a ``zorder`` keyword argument to be compatible with this
class. If not, an exception will be raised when attempting to plot the element.
*args
Positional arguments to pass to ``axes.meth``.
label : str, optional
Label to be attached to the :class:`~graphinglib.graph_elements.PlottableAxMethod`.
**kwargs
Keyword arguments to pass to ``axes.meth``.
"""
[docs]
def __init__(self, meth: str, *args, label: Optional[str] = None, **kwargs) -> None:
self.meth = meth
self.args = args
self.kwargs = kwargs
self.label = label
self.handle = None
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>`_.
"""
try:
attrs = getattr(axes, self.meth)(*self.args, zorder=z_order, **self.kwargs)
if isinstance(attrs, list) and len(attrs) > 0:
self.handle = attrs[0]
except TypeError as e:
if "zorder" in str(e):
try:
attrs = getattr(axes, self.meth)(*self.args, **self.kwargs)
if isinstance(attrs, list) and len(attrs) > 0:
self.handle = attrs[0]
except Exception as e2:
raise GraphingException(
f"Failed to call Axes method '{self.meth}' with provided arguments. Please check that all "
"provided arguments are valid for the given method."
) from e2
else:
raise GraphingException(
f"Failed to call Axes method '{self.meth}' with provided arguments. Please check that all "
"provided arguments are valid for the given method."
) from e