Source code for graphinglib.data_plotting_1d

from __future__ import annotations

from .inherit import INHERIT, Inherit, is_inherit

from copy import deepcopy
from dataclasses import dataclass
from types import NoneType
from typing import Callable, Optional, Protocol, runtime_checkable

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import Colormap, Normalize, is_color_like, to_rgba
from matplotlib.patches import Polygon
from numpy.typing import ArrayLike
from pyperclip import copy as copy_to_clipboard
from scipy.integrate import cumulative_trapezoid
from scipy.interpolate import interp1d

from .graph_elements import Plottable, Point
from .tools import MathematicalObject, get_contrasting_shade

try:
    from typing import Self
except ImportError:
    from typing_extensions import Self


@runtime_checkable
class Fit(Protocol):
    """
    Dummy class to allow type hinting of Fit objects.
    """

    def _plot_element(self, axes: plt.Axes, z_order: int) -> None:
        """
        Plots the element in the specified axes.
        """
        pass

    def show_residual_curves(
        self,
        sigma_multiplier: float,
        color: str,
        line_width: float,
        line_style: str,
    ) -> None:
        pass

    def get_residuals(self) -> np.ndarray:
        pass


@runtime_checkable
class Plottable1D(Plottable, Protocol):
    """
    Dummy class to allow type hinting of Plottable1D objects.
    """

    @staticmethod
    def to_desmos(
        x_data: ArrayLike, y_data: ArrayLike, decimal_precision: int = 2
    ) -> str:
        """
        Gives the data points in a Desmos-readable format. The outputted string can then be pasted into a single Desmos
        cell and the object's data will be displayed.

        .. note::
            NaN values are ignored.

        Parameters
        ----------
        x_data, y_data : ArrayLike
            Arrays of x and y values to be plotted.
        decimal_precision : int, optional
            Specifies the number of decimals of the formatted points.
            Defaults to 2.

        Returns
        -------
        formatted points : str
            A list of tuples representing every data point.
        """
        sorted_indices = np.argsort(x_data)
        sorted_x_data = x_data[sorted_indices]
        sorted_y_data = y_data[sorted_indices]

        # Change exponential formatting to be interpretable by Desmos
        def format_tex(num: str, exponent: str):
            num = num.rstrip("0")
            if exponent == "+00":
                return str(num)
            else:
                return rf"{num}\cdot10^" + "{" + str(int(exponent)) + "}"

        formatted_points = "["
        for x, y in zip(sorted_x_data, sorted_y_data):
            if np.isnan(x) or np.isnan(y):
                continue
            x_num, x_exponent = f"{x:.{decimal_precision:d}e}".split("e")
            y_num, y_exponent = f"{y:.{decimal_precision:d}e}".split("e")
            formatted_points += (
                f"({format_tex(x_num, x_exponent)},{format_tex(y_num, y_exponent)}),"
            )
        formatted_points = formatted_points[:-1] + "]"
        return formatted_points


[docs] @dataclass class Curve(Plottable1D, MathematicalObject): """ This class implements a general continuous curve. Parameters ---------- x_data, y_data : ArrayLike Arrays of x and y values to be plotted. label : str, optional Label to be displayed in the legend. color : str Color of the curve. Default depends on the ``figure_style`` configuration. line_width : float Width of the curve. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the curve. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the curve. 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_data: ArrayLike, y_data: ArrayLike, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, ) -> None: self.handle = None self._x_data = np.asarray(x_data) self._y_data = np.asarray(y_data) self._label = label self._color = color self._line_width = line_width self._line_style = line_style self._alpha = alpha self._x_error = None self._y_error = None self._show_errorbars: bool = False self._errorbars_color = None self._errorbars_line_width = None self._cap_thickness = None self._cap_width = None self._show_error_curves: bool = False self._error_curves_fill_between: bool = False self._error_curves_color = None self._error_curves_line_style = None self._error_curves_line_width = None self._fill_between_bounds: Optional[tuple[float, float]] = None self._fill_between_other_curve: Optional[Self] = None self._fill_between_color: Optional[str] = None
[docs] @classmethod def from_function( cls, func: Callable[[ArrayLike], ArrayLike], x_min: float, x_max: float, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, number_of_points: int = 500, ) -> Self: """ Creates a :class:`~graphinglib.data_plotting_1d.Curve` from a function and a range of x values. Parameters ---------- func : Callable[[ArrayLike], ArrayLike] Function to be plotted. Works with regular functions and lambda functions. x_min, x_max : float The :class:`~graphinglib.data_plotting_1d.Curve` will be plotted between these two values. label : str, optional Label to be displayed in the legend. color : str Color of the curve. Default depends on the ``figure_style`` configuration. line_width : float Width of the curve. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the curve. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the curve. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. number_of_points : int Number of points to be used to plot the curve (resolution). Defaults to 500. 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_1d.Curve` object created from the given function and x range. """ x_data = np.linspace(x_min, x_max, number_of_points) y_data = func(x_data) return cls(x_data, y_data, label, color, line_width, line_style, alpha)
@property def x_data(self) -> np.ndarray: 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) -> np.ndarray: return self._y_data @y_data.setter def y_data(self, y_data: ArrayLike) -> None: self._y_data = np.asarray(y_data) @property def x_error(self) -> np.ndarray | None: return self._x_error @x_error.setter def x_error(self, x_error: ArrayLike) -> None: self._x_error = np.asarray(x_error) @property def y_error(self) -> np.ndarray | None: return self._y_error @y_error.setter def y_error(self, y_error: ArrayLike) -> None: self._y_error = np.asarray(y_error) @property def label(self) -> Optional[str]: return self._label @label.setter def label(self, label: Optional[str]) -> None: self._label = label @property def color(self) -> str: return self._color @color.setter def color(self, color: str) -> None: self._color = color @property def line_width(self) -> float | Inherit: return self._line_width @line_width.setter def line_width(self, line_width: float | Inherit) -> None: self._line_width = line_width @property def line_style(self) -> str: return self._line_style @line_style.setter def line_style(self, line_style: str) -> None: self._line_style = line_style @property def alpha(self) -> float | Inherit: return self._alpha @alpha.setter def alpha(self, alpha: float | Inherit) -> None: self._alpha = alpha @property def show_errorbars(self) -> bool: return self._show_errorbars @show_errorbars.setter def show_errorbars(self, show_errorbars: bool) -> None: self._show_errorbars = show_errorbars @property def errorbars_color(self) -> str: return self._errorbars_color @errorbars_color.setter def errorbars_color(self, errorbars_color: str) -> None: self._errorbars_color = errorbars_color @property def errorbars_line_width(self) -> float | Inherit: return self._errorbars_line_width @errorbars_line_width.setter def errorbars_line_width(self, errorbars_line_width: float | Inherit) -> None: self._errorbars_line_width = errorbars_line_width @property def cap_thickness(self) -> float | Inherit: return self._cap_thickness @cap_thickness.setter def cap_thickness(self, cap_thickness: float | Inherit) -> None: self._cap_thickness = cap_thickness @property def cap_width(self) -> float | Inherit: return self._cap_width @cap_width.setter def cap_width(self, cap_width: float | Inherit) -> None: self._cap_width = cap_width @property def show_error_curves(self) -> bool: return self._show_error_curves @show_error_curves.setter def show_error_curves(self, show_error_curves: bool) -> None: self._show_error_curves = show_error_curves @property def error_curves_fill_between(self) -> bool: return self._error_curves_fill_between @error_curves_fill_between.setter def error_curves_fill_between(self, error_curves_fill_between: bool) -> None: self._error_curves_fill_between = error_curves_fill_between @property def error_curves_color(self) -> str: return self._error_curves_color @error_curves_color.setter def error_curves_color(self, error_curves_color: str) -> None: self._error_curves_color = error_curves_color @property def error_curves_line_style(self) -> str: return self._error_curves_line_style @error_curves_line_style.setter def error_curves_line_style(self, error_curves_line_style: str) -> None: self._error_curves_line_style = error_curves_line_style @property def error_curves_line_width(self) -> float | Inherit: return self._error_curves_line_width @error_curves_line_width.setter def error_curves_line_width(self, error_curves_line_width: float | Inherit) -> None: self._error_curves_line_width = error_curves_line_width @property def fill_between_bounds(self) -> tuple[float, float]: return self._fill_between_bounds @fill_between_bounds.setter def fill_between_bounds(self, fill_between_bounds: tuple[float, float]) -> None: self._fill_between_bounds = fill_between_bounds @property def fill_between_other_curve(self) -> Self: return self._fill_between_other_curve @fill_between_other_curve.setter def fill_between_other_curve(self, fill_between_other_curve: Self) -> None: self._fill_between_other_curve = fill_between_other_curve @property def fill_between_color(self) -> str: return self._fill_between_color @fill_between_color.setter def fill_between_color(self, fill_between_color: str) -> None: self._fill_between_color = fill_between_color def __eq__(self, other: Self) -> bool: """ Defines the equality between two curves. """ return ( np.equal(self.x_data, other.x_data).all() and np.equal(self.y_data, other.y_data).all() ) def __add__(self, other: Self | float) -> Self: """ Defines the addition of two curves or a curve and a number. """ if isinstance(other, Curve): if not np.array_equal(self._x_data, other._x_data): if len(self._x_data) > len(other._x_data): x_data = other._x_data y_data = interp1d(self._x_data, self._y_data)(x_data) return Curve(x_data, y_data + other._y_data) else: x_data = self._x_data y_data = interp1d(other._x_data, other._y_data)(x_data) return Curve(x_data, y_data + self._y_data) new_y_data = self._y_data + other._y_data return Curve(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data + other return Curve(self._x_data, new_y_data) else: raise TypeError("Can only add a curve to another curve or a number.") def __sub__(self, other: Self | float) -> Self: """ Defines the subtraction of two curves or a curve and a number. """ if isinstance(other, Curve): if not np.array_equal(self._x_data, other._x_data): if len(self._x_data) > len(other._x_data): x_data = other._x_data y_data = interp1d(self._x_data, self._y_data)(x_data) return Curve(x_data, y_data - other._y_data) else: x_data = self._x_data y_data = interp1d(other._x_data, other._y_data)(x_data) return Curve(x_data, self._y_data - y_data) new_y_data = self._y_data - other._y_data return Curve(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data - other return Curve(self._x_data, new_y_data) else: raise TypeError("Can only subtract a curve from another curve or a number.") def __mul__(self, other: Self | float) -> Self: """ Defines the multiplication of two curves or a curve and a number. """ if isinstance(other, Curve): if not np.array_equal(self._x_data, other._x_data): if len(self._x_data) > len(other._x_data): x_data = other._x_data y_data = interp1d(self._x_data, self._y_data)(x_data) return Curve(x_data, y_data * other._y_data) else: x_data = self._x_data y_data = interp1d(other._x_data, other._y_data)(x_data) return Curve(x_data, y_data * self._y_data) new_y_data = self._y_data * other._y_data return Curve(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data * other return Curve(self._x_data, new_y_data) else: raise TypeError("Can only multiply a curve by another curve or a number.") def __truediv__(self, other: Self | float) -> Self: """ Defines the division of two curves or a curve and a number. """ if isinstance(other, Curve): if not np.array_equal(self._x_data, other._x_data): if len(self._x_data) > len(other._x_data): x_data = other._x_data y_data = interp1d(self._x_data, self._y_data)(x_data) return Curve(x_data, y_data / other._y_data) else: x_data = self._x_data y_data = interp1d(other._x_data, other._y_data)(x_data) return Curve(x_data, self._y_data / y_data) new_y_data = self._y_data / other._y_data return Curve(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data / other return Curve(self._x_data, new_y_data) else: raise TypeError("Can only divide a curve by another curve or a number.") def __pow__(self, other: float) -> Self: """ Defines the power of a curve to a number. """ if isinstance(other, (int, float)): new_y_data = self._y_data**other return Curve(self._x_data, new_y_data) else: raise TypeError("Can only raise a curve to another curve or a number.") def __iter__(self): """ Defines the iteration of a curve. Returns the y values. """ return iter(self._y_data) def __abs__(self) -> Self: """ Returns the absolute value of the curve. """ return Curve(self._x_data, np.abs(self._y_data))
[docs] def copy(self) -> Self: """ Returns a deep copy of the :class:`~graphinglib.data_plotting_1d.Curve`. """ return deepcopy(self)
[docs] def create_slice_x( self, x1: float, x2: float, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a slice of the curve between two x values. Parameters ---------- x1, x2 : float The x values between which the slice is to be created. label : str, optional Label of the slice to be displayed in the legend. color : str Color of the slice. Default depends on the ``figure_style`` configuration. line_width : float Width of the slice. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the slice. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the slice. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the curve (with all its parameters) will be returned with the slicing applied. Any other parameters passed to this method will also be applied to the copied curve. If ``False``, a new curve will be created with the slicing applied and the parameters passed to this method. 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_1d.Curve` object which is the slice of the original curve between the two x values. """ mask = (self._x_data >= x1) & (self._x_data <= x2) x_data = self._x_data[mask] y_data = self._y_data[mask] if copy_first: copy = self.copy() copy._x_data = x_data copy._y_data = y_data if label is not None: copy._label = label if color != INHERIT: copy._color = color if line_width != INHERIT: copy._line_width = line_width if line_style != INHERIT: copy._line_style = line_style if alpha != INHERIT: copy._alpha = alpha return copy else: return Curve(x_data, y_data, label, color, line_width, line_style, alpha)
[docs] def create_slice_y( self, y1: float, y2: float, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a slice of the curve between two y values. Parameters ---------- y1, y2 : float The y values between which the slice is to be created. label : str, optional Label of the slice to be displayed in the legend. color : str Color of the slice. Default depends on the ``figure_style`` configuration. line_width : float Width of the slice. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the slice. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the slice. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the curve (with all its parameters) will be returned with the slicing applied. Any other parameters passed to this method will also be applied to the copied curve. If ``False``, a new curve will be created with the slicing applied and the parameters passed to this method. 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_1d.Curve` object which is the slice of the original curve between the two y values. """ mask = (self._y_data >= y1) & (self._y_data <= y2) x_data = self._x_data[mask] y_data = self._y_data[mask] if copy_first: copy = self.copy() copy._x_data = x_data copy._y_data = y_data if label is not None: copy._label = label if color != INHERIT: copy._color = color if line_width != INHERIT: copy._line_width = line_width if line_style != INHERIT: copy._line_style = line_style if alpha != INHERIT: copy._alpha = alpha return copy else: return Curve(x_data, y_data, label, color, line_width, line_style, alpha)
[docs] def add_errorbars( self, x_error: Optional[ArrayLike] = None, y_error: Optional[ArrayLike] = None, cap_width: float | Inherit = INHERIT, errorbars_color: str | Inherit = INHERIT, errorbars_line_width: float | Inherit = INHERIT, cap_thickness: float | Inherit = INHERIT, ) -> None: """ Adds errorbars to the :class:`~graphinglib.data_plotting_1d.Curve`. Parameters ---------- x_error, y_error : ArrayLike, optional Arrays of x and y errors. Use one or both. Values must be non-negative. cap_width : float Width of the errorbar caps. Typical range is ``0`` to ``10`` points. Default depends on the ``figure_style`` configuration. errorbars_color : str Color of the errorbars. ``"same as curve"`` uses the curve color. Default depends on the ``figure_style`` configuration. errorbars_line_width : float Width of the errorbars. Typical range is ``0.5`` to ``3`` points. Default depends on the ``figure_style`` configuration. cap_thickness : float Thickness of the errorbar caps. Typical range is ``0.5`` to ``3`` points. 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._show_errorbars = True if x_error is not None: self._x_error = np.array(x_error) if y_error is not None: self._y_error = np.array(y_error) self._errorbars_color = errorbars_color self._errorbars_line_width = errorbars_line_width self._cap_thickness = cap_thickness self._cap_width = cap_width
[docs] def add_error_curves( self, y_error: Optional[ArrayLike] = None, error_curves_color: str | Inherit = INHERIT, error_curves_line_style: str | Inherit = INHERIT, error_curves_line_width: float | Inherit = INHERIT, error_curves_fill_between: bool | Inherit = INHERIT, ) -> None: """ Adds error curves to the :class:`~graphinglib.data_plotting_1d.Curve`. Parameters ---------- y_error : ArrayLike, optional Array of y errors. error_curves_color : str Color of the error curves. ``"same as curve"`` uses the curve color. Default depends on the ``figure_style`` configuration. error_curves_line_style : str Line style of the error curves. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. error_curves_line_width : float Line width of the error curves. Typical range is ``0.5`` to ``3`` points. Default depends on the ``figure_style`` configuration. error_curves_fill_between : bool Whether or not to fill the area between the two error curves. 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._show_error_curves = True if y_error is not None: self._y_error = np.array(y_error) self._error_curves_color = error_curves_color self._error_curves_line_style = error_curves_line_style self._error_curves_line_width = error_curves_line_width self._error_curves_fill_between = error_curves_fill_between
[docs] def get_coordinates_at_x( self, x: float, interpolation_method: str = "linear", ) -> tuple[float, float]: """ Gets the coordinates of the curve at a given x value. Parameters ---------- x : float The x value of the desired coordinates. interpolation_method : str, The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". Returns ------- tuple[float, float] The coordinates of the curve at the given x value. """ return ( x, float(interp1d(self._x_data, self._y_data, kind=interpolation_method)(x)), )
[docs] def create_point_at_x( self, x: float, interpolation_method: str = "linear", label: Optional[str] = None, face_color: str | Inherit = INHERIT, edge_color: str | Inherit = INHERIT, marker_size: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, alpha: float | Inherit = INHERIT, ) -> Point: """ Creates a point on the curve at a given x value. Parameters ---------- x : float The x value of the point. interpolation_method : str, The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". label : str, optional Point's label to be displayed in the legend. face_color : str Face color of the point. Default depends on the ``figure_style`` configuration. edge_color : str Edge color of the point. Default depends on the ``figure_style`` configuration. marker_size : float Size of the point. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_style : str Style of the point. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. line_width : float Width of the point edge. Typical range is ``0`` to ``3`` points. 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. 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 ------- :class:`~graphinglib.graph_elements.Point` The point on the curve at the given x value. """ point = Point( x, self.get_coordinates_at_x(x, interpolation_method)[1], label=label, face_color=face_color, edge_color=edge_color, marker_size=marker_size, marker_style=marker_style, edge_width=line_width, alpha=alpha, ) return point
[docs] def get_coordinates_at_y( self, y: float, interpolation_method: str = "linear", ) -> list[tuple[float, float]]: """ Gets the coordinates of the curve at a given y value. Can return multiple coordinate pairs if the curve crosses the y value multiple times. Parameters ---------- y : float The y value of the desired coordinates. interpolation_method : str, The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". Returns ------- list[tuple[float, float]] The coordinates of the points on the curve at the given y value. """ xs = self._x_data ys = self._y_data crossings = np.where(np.diff(np.sign(ys - y)))[0] x_vals: list[float] = [] for cross in crossings: x1, x2 = xs[cross], xs[cross + 1] y1, y2 = ys[cross], ys[cross + 1] f = interp1d([y1, y2], [x1, x2], kind=interpolation_method) x_val = f(y) x_vals.append(float(x_val)) points = [(x_val, y) for x_val in x_vals] return points
[docs] def create_points_at_y( self, y: float, interpolation_method: str = "linear", label: str | None = None, face_color: str | Inherit = INHERIT, edge_color: str | Inherit = INHERIT, marker_size: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, alpha: float | Inherit = INHERIT, ) -> list[Point]: """ Gets the points on the curve at a given y value. Can return multiple Point objects if the curve crosses the y value multiple times. Parameters ---------- y : float The y value of the desired points. interpolation_method : str The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". label : str, optional Point label to be displayed in the legend. face_color : str Face color of the point. Default depends on the ``figure_style`` configuration. edge_color : str Edge color of the point. Default depends on the ``figure_style`` configuration. marker_size : float Size of the point. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_style : str Style of the point. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. line_width : float Width of the point edge. Typical range is ``0`` to ``3`` points. 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. 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 ------- list[:class:`~graphinglib.graph_elements.Point`] The Point objects on the curve at the given y value. """ pairs = self.get_coordinates_at_y(y, interpolation_method) points = [ Point( pair[0], pair[1], label=label, face_color=face_color, edge_color=edge_color, marker_size=marker_size, marker_style=marker_style, edge_width=line_width, alpha=alpha, ) for pair in pairs ] return points
[docs] def create_derivative_curve( self, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a new curve which is the derivative of the original curve. Parameters ---------- label : str, optional Label of the new curve to be displayed in the legend. color : str Color of the new curve. Default depends on the ``figure_style`` configuration. line_width : float Width of the new curve. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the new curve. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the new curve. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the curve (with all its parameters) will be returned with the derivative applied. Any other parameters passed to this method will also be applied to the copied curve. If ``False``, a new curve will be created with the derivative applied and the parameters passed to this method. 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_1d.Curve` object which is the derivative of the original curve. """ x_data = self._x_data y_data = np.gradient(self._y_data, x_data) if copy_first: copy = self.copy() copy._y_data = y_data if label is not None: copy._label = label if color != INHERIT: copy._color = color if line_width != INHERIT: copy._line_width = line_width if line_style != INHERIT: copy._line_style = line_style if alpha != INHERIT: copy._alpha = alpha return copy else: return Curve(x_data, y_data, label, color, line_width, line_style, alpha)
[docs] def create_integral_curve( self, initial_value: float = 0, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a new curve which is the integral of the original curve. Parameters ---------- initial_value : float, optional The value of the integral at the first x value (initial condition). Defaults to 0. label : str, optional Label of the new curve to be displayed in the legend. color : str Color of the new curve. Default depends on the ``figure_style`` configuration. line_width : float Width of the new curve. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the new curve. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the new curve. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the curve (with all its parameters) will be returned with the integral applied. Any other parameters passed to this method will also be applied to the copied curve. If ``False``, a new curve will be created with the integral applied and the parameters passed to this method. 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_1d.Curve` object which is the integral of the original curve. """ # calculate the integral curve using cumulative trapezoidal integration y_data = ( cumulative_trapezoid(self._y_data, self._x_data, initial=0) + initial_value ) if copy_first: copy = self.copy() copy._y_data = y_data if label is not None: copy._label = label if color != INHERIT: copy._color = color if line_width != INHERIT: copy._line_width = line_width if line_style != INHERIT: copy._line_style = line_style if alpha != INHERIT: copy._alpha = alpha return copy else: return Curve( self._x_data, y_data, label, color, line_width, line_style, alpha )
[docs] def create_tangent_curve( self, x: float, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a new curve which is the tangent to the original curve at a given x value. Parameters ---------- x : float The x value at which the tangent is to be calculated. label : str, optional Label of the new curve to be displayed in the legend. color : str Color of the new curve. Default depends on the ``figure_style`` configuration. line_width : float Width of the new curve. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the new curve. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the new curve. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the curve (with all its parameters) will be returned with the tangent applied. Any other parameters passed to this method will also be applied to the copied curve. If ``False``, a new curve will be created with the tangent applied and the parameters passed to this method. 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 ------- :class:`~graphinglib.data_plotting_1d.Curve` A :class:`~graphinglib.data_plotting_1d.Curve` object which is the tangent to the original curve at a given x value. """ point = self.get_coordinates_at_x(x) gradient = self.create_derivative_curve().get_coordinates_at_x(x)[1] y_data = gradient * (self._x_data - x) + point[1] if copy_first: copy = self.copy() copy._y_data = y_data if label is not None: copy._label = label if color != INHERIT: copy._color = color if line_width != INHERIT: copy._line_width = line_width if line_style != INHERIT: copy._line_style = line_style if alpha != INHERIT: copy._alpha = alpha return copy else: tangent_curve = Curve( self._x_data, y_data, label, color, line_width, line_style, alpha ) return tangent_curve
[docs] def create_normal_curve( self, x: float, label: Optional[str] = None, color: str | Inherit = INHERIT, line_width: float | Inherit = INHERIT, line_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a new curve which is the normal to the original curve at a given x value. Parameters ---------- x : float The x value at which the normal is to be calculated. label : str, optional Label of the new curve to be displayed in the legend. color : str Color of the new curve. Default depends on the ``figure_style`` configuration. line_width : float Width of the new curve. Typical range is ``0.5`` to ``4``. Default depends on the ``figure_style`` configuration. line_style : str Style of the new curve. Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and ``"dotted"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the new curve. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the curve (with all its parameters) will be returned with the normal applied. Any other parameters passed to this method will also be applied to the copied curve. If ``False``, a new curve will be created with the normal applied and the parameters passed to this method. 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 ------- :class:`~graphinglib.data_plotting_1d.Curve` A :class:`~graphinglib.data_plotting_1d.Curve` object which is the normal to the original curve at a given x value. """ point = self.get_coordinates_at_x(x) gradient = self.create_derivative_curve().get_coordinates_at_x(x)[1] y_data = -1 / gradient * (self._x_data - x) + point[1] if copy_first: copy = self.copy() copy._y_data = y_data if label is not None: copy._label = label if color != INHERIT: copy._color = color if line_width != INHERIT: copy._line_width = line_width if line_style != INHERIT: copy._line_style = line_style if alpha != INHERIT: copy._alpha = alpha return copy else: normal_curve = Curve( self._x_data, y_data, label, color, line_width, line_style, alpha ) return normal_curve
[docs] def get_slope_at(self, x: float) -> float: """ Calculates the slope of the curve at a given x value. Parameters ---------- x : float The x value at which the slope is to be calculated. Returns ------- The slope of the curve (float) at the given x value. """ return self.create_derivative_curve().get_coordinates_at_x(x)[1]
[docs] def get_arc_length_between(self, x1: float, x2: float) -> float: """ Calculates the arc length of the curve between two x values. Parameters ---------- x1, x2 : float The x values between which the arc length is to be calculated. Returns ------- The arc length of the curve (float) between the two given x values. """ y_data = self._y_data x_data = self._x_data # f = interp1d(x_data, y_data) # x = np.linspace(x1, x2, 1000) # y = f(x) x = x_data[(x_data >= x1) & (x_data <= x2)] y = y_data[(x_data >= x1) & (x_data <= x2)] return np.trapezoid(np.sqrt(1 + np.gradient(y, x) ** 2), x)
[docs] def get_area_between( self, x1: float, x2: float, fill_between: bool = False, fill_color: str | Inherit = INHERIT, other_curve: Optional[Self] = None, ) -> float: """ Calculates the area between the curve and the x axis between two x values. This is the definite integral of the curve between the two x values. Parameters ---------- x1, x2 : float The x values between which the area is to be calculated. fill_between : bool Whether to fill the specified area between the curve and the x axis when displaying. Defaults to ``False``. fill_color : str Color of the area between the curve and the x axis when ``fill_between`` is set to ``True``. ``"same as curve"`` uses the curve color. Default depends on the ``figure_style`` configuration. other_curve : :class:`~graphinglib.data_plotting_1d.Curve`, optional If specified, the area between the two curves will be calculated instead of the area between the curve and the x axis. 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 ------- The area (float) between the curve and the x axis (or between the two curves) between the two given x values. """ if other_curve is None: if fill_between: self._fill_between_bounds = (x1, x2) self._fill_between_color = fill_color y_data = self._y_data x_data = self._x_data mask = (x_data >= x1) & (x_data <= x2) y = y_data[mask] x = x_data[mask] return np.trapezoid(y, x) else: if fill_between: self._fill_between_bounds = (x1, x2) self._fill_between_color = fill_color self._fill_between_other_curve = other_curve if np.array_equal(self._x_data, other_curve._x_data): # No need to interpolate mask = (self._x_data >= x1) & (self._x_data <= x2) common_x = self._x_data[mask] y1 = self._y_data[mask] y2 = other_curve._y_data[mask] else: # Interpolate to get common x values density_x1 = len(self._x_data) / (self._x_data[-1] - self._x_data[0]) density_x2 = len(other_curve._x_data) / ( other_curve._x_data[-1] - other_curve._x_data[0] ) higher_density = max(density_x1, density_x2) num_of_values = int(np.ceil(higher_density * (x2 - x1))) common_x = np.linspace(x1, x2, num_of_values) y1 = np.interp(common_x, self._x_data, self._y_data) y2 = np.interp(common_x, other_curve._x_data, other_curve._y_data) difference = y1 - y2 area = np.trapezoid(difference, common_x) return area
[docs] def get_intersection_coordinates( self, other: Self, ) -> list[tuple[float, float]]: """ Calculates the coordinates of the intersection points between two curves. Parameters ---------- other : :class:`~graphinglib.data_plotting_1d.Curve` The other curve to calculate the intersections with. Returns ------- list[tuple[float, float]] A list of tuples of coordinates which are the intersection points between the two curves. """ y = self._y_data - other._y_data s = np.abs(np.diff(np.sign(y))).astype(bool) intersections_x = self._x_data[:-1][s] + np.diff(self._x_data)[s] / ( np.abs(y[1:][s] / y[:-1][s]) + 1 ) intersections_y = np.interp(intersections_x, self._x_data, self._y_data) points = [] for i in range(len(intersections_x)): x_val = intersections_x[i] y_val = intersections_y[i] points.append((x_val, y_val)) return points
[docs] def to_desmos(self, decimal_precision: int = 2, to_clipboard: bool = False) -> str: """ Gives the data points in a Desmos-readable format. The outputted string can then be pasted into a single Desmos cell and the object's data will be displayed. Parameters ---------- decimal_precision : int, optional Specifies the number of decimals of the formatted points. Defaults to 2. to_clipboard : bool, optional Specifies whether the points should be directly copied to the user's clipboard in addition to being returned as a string. Defaults to False. Returns ------- formatted points : str A list of tuples representing every data point. """ formatted_points = super().to_desmos( self._x_data, self._y_data, decimal_precision ) if to_clipboard: copy_to_clipboard(formatted_points) return formatted_points
[docs] def create_intersection_points( self, other: Self, labels: Optional[list[str] | str] = None, face_colors: list[str] | str | Inherit = INHERIT, edge_colors: list[str] | str | Inherit = INHERIT, marker_sizes: list[float] | float | Inherit = INHERIT, marker_styles: list[str] | str | Inherit = INHERIT, edge_widths: list[float] | float | Inherit = INHERIT, alphas: list[float] | float | Inherit = INHERIT, ) -> list[Point]: """ Creates the intersection Points between two curves. Parameters ---------- other : :class:`~graphinglib.data_plotting_1d.Curve` The other curve to calculate the intersections with. as_point_objects : bool Whether to return a list of :class:`~graphinglib.graph_elements.Point` objects (True) or a list of tuples of coordinates (False). Defaults to False. labels : list[str] or str, optional Labels of the intersection points to be displayed in the legend. If a single string is passed, all intersection points will have the same label. face_colors : list[str] or str Face colors of the intersection points. If a single string is passed, all intersection points will have the same color. Default depends on the ``figure_style`` configuration. edge_colors : list[str] or str Edge colors of the intersection points. If a single string is passed, all intersection points will have the same color. Default depends on the ``figure_style`` configuration. marker_sizes : list[float] or float Sizes of the intersection points. If a single float is passed, all intersection points will have the same size. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_styles : list[str] or str Styles of the intersection points. If a single string is passed, all intersection points will have the same style. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. edge_widths : list[float] or float Widths of the intersection points. If a single float is passed, all intersection points will have the same width. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. alphas : list[float] or float Opacities of the intersection points. If a single float is passed, all intersection points will have the same opacity. 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)``). Returns ------- list[:class:`~graphinglib.graph_elements.Point`] or list[tuple[float, float]] A list of :class:`~graphinglib.graph_elements.Point` objects which are the intersection points between the two curves. """ y = self._y_data - other._y_data s = np.abs(np.diff(np.sign(y))).astype(bool) intersections_x = self._x_data[:-1][s] + np.diff(self._x_data)[s] / ( np.abs(y[1:][s] / y[:-1][s]) + 1 ) point_coords = self.get_intersection_coordinates(other) point_objects = [] for i in range(len(intersections_x)): try: assert isinstance(labels, list) label = labels[i] except (IndexError, TypeError, AssertionError): label = labels try: assert isinstance(face_colors, list) face_color = face_colors[i] except (IndexError, TypeError, AssertionError): face_color = face_colors try: assert isinstance(edge_colors, list) edge_color = edge_colors[i] except (IndexError, TypeError, AssertionError): edge_color = edge_colors try: assert isinstance(marker_sizes, list) marker_size = marker_sizes[i] except (IndexError, TypeError, AssertionError): marker_size = marker_sizes try: assert isinstance(marker_styles, list) marker_style = marker_styles[i] except (IndexError, TypeError, AssertionError): marker_style = marker_styles try: assert isinstance(edge_widths, list) edge_width = edge_widths[i] except (IndexError, TypeError, AssertionError): edge_width = edge_widths try: assert isinstance(alphas, list) alpha = alphas[i] except (IndexError, TypeError, AssertionError): alpha = alphas point = point_coords[i] point_objects.append( Point( point[0], point[1], label=label, face_color=face_color, edge_color=edge_color, marker_size=marker_size, marker_style=marker_style, edge_width=edge_width, alpha=alpha, ) ) return point_objects
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None: """ Plots the element in the specified axes. """ params = { "color": self._color, "linewidth": self._line_width, "linestyle": self._line_style, "alpha": self._alpha, } if self._show_errorbars: params.update( { "elinewidth": ( self._errorbars_line_width if self._errorbars_line_width != "same as curve" else self._line_width ), "capsize": self._cap_width, "capthick": ( self._cap_thickness if self._cap_thickness != "same as curve" else self._line_width ), "ecolor": ( self._errorbars_color if self._errorbars_color != "same as curve" else self._color ), } ) params = {k: v for k, v in params.items() if v != INHERIT} self.handle = axes.errorbar( self._x_data, self._y_data, xerr=self._x_error, yerr=self._y_error, label=self._label, zorder=z_order, **params, ) else: params = {k: v for k, v in params.items() if v != INHERIT} self.handle = axes.errorbar( self._x_data, self._y_data, label=self._label, zorder=z_order, **params, ) if self._show_error_curves: max_y = ( self._y_data + self._y_error if self._y_error is not None else self._y_data ) min_y = ( self._y_data - self._y_error if self._y_error is not None else self._y_data ) params = { "color": ( self._error_curves_color if self._error_curves_color != "same as curve" else self.handle[0].get_color() ), "linestyle": ( self._error_curves_line_style if self._error_curves_line_style != "same as curve" else self._line_style ), "linewidth": ( self._error_curves_line_width if self._error_curves_line_width != "same as curve" else self._line_width ), } params = {k: v for k, v in params.items() if v != INHERIT} axes.plot( self._x_data, min_y, **params, ) axes.plot( self._x_data, max_y, **params, ) if self._error_curves_fill_between: axes.fill_between( self._x_data, max_y, min_y, facecolor=self.handle[0].get_color(), alpha=0.2, ) if self._fill_between_bounds: params = {"alpha": 0.2} params["facecolor"] = ( self._fill_between_color if self._fill_between_color != "same as curve" else self.handle[0].get_color() ) params = {k: v for k, v in params.items() if v != INHERIT} if self._fill_between_other_curve: self_y_data = self._y_data self_x_data = self._x_data other_y_data = self._fill_between_other_curve._y_data other_x_data = self._fill_between_other_curve._x_data x_data = np.linspace( self._fill_between_bounds[0], self._fill_between_bounds[1], max(len(self_x_data), len(other_x_data)), ) self_y_data = interp1d(self_x_data, self_y_data)(x_data) other_y_data = interp1d(other_x_data, other_y_data)(x_data) params["x"] = x_data params["y1"] = self_y_data params["y2"] = other_y_data where_x_data = x_data else: params["x"] = self._x_data params["y1"] = self._y_data where_x_data = self._x_data axes.fill_between( where=np.logical_and( where_x_data >= self._fill_between_bounds[0], where_x_data <= self._fill_between_bounds[1], ), zorder=z_order - 2, **params, )
[docs] @dataclass class Scatter(Plottable1D, MathematicalObject): """ This class implements a general scatter plot. Parameters ---------- x_data, y_data : ArrayLike Arrays of x and y values to be plotted. label : str, optional Label to be displayed in the legend. face_color : str or ArrayLike or None Face color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker faces are transparent. Default depends on the ``figure_style`` configuration. edge_color : str or ArrayLike or None Edge color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker edges are transparent. Default depends on the ``figure_style`` configuration. color_map : str or Colormap Color map used when ``face_color`` or ``edge_color`` is an array of intensity values. 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. marker_size : float Size of the points. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_edge_width: float Line width of the marker edges. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. marker_style : str Style of the points. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the scatter plot. 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)``). They may also be arrays of intensity values, which are mapped through ``color_map``. """
[docs] def __init__( self, x_data: ArrayLike, y_data: ArrayLike, label: Optional[str] = None, face_color: str | ArrayLike | NoneType | Inherit = INHERIT, edge_color: str | ArrayLike | NoneType | Inherit = INHERIT, color_map: str | Colormap | Inherit = INHERIT, color_map_range: Optional[tuple[float, float]] = None, show_color_bar: bool | Inherit = INHERIT, marker_size: float | Inherit = INHERIT, marker_edge_width: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, ) -> None: """ This class implements a general scatter plot. Parameters ---------- x_data, y_data : ArrayLike Arrays of x and y values to be plotted. label : str, optional Label to be displayed in the legend. face_color : str or ArrayLike or None Face color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker faces are transparent. Default depends on the ``figure_style`` configuration. edge_color : str or ArrayLike or None Edge color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker edges are transparent. Default depends on the ``figure_style`` configuration. color_map : str or Colormap Color map used when ``face_color`` or ``edge_color`` is an array of intensity values. 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. marker_size : float Size of the points. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_edge_width: float Line width of the marker edges. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. marker_style : str Style of the points. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the scatter plot. 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)``). They may also be arrays of intensity values, which are mapped through ``color_map``. """ self.handle = None self.errorbars_handle = None self._x_data = np.asarray(x_data) self._y_data = np.asarray(y_data) self._label = label self._face_color = face_color self._edge_color = edge_color self._color_map = color_map self._color_map_range = color_map_range self._show_color_bar = show_color_bar self._marker_size = marker_size self._marker_edge_width = marker_edge_width self._marker_style = marker_style self._alpha = alpha self._x_error = None self._y_error = None self._show_errorbars: bool = False self._errorbars_line_width: float = 1.0 self._cap_width: float = 3.0 self._cap_thickness: float = 1.0 self._errorbars_color: Optional[str] = None self._color_bar_params: dict = {}
[docs] @classmethod def from_function( cls, func: Callable[[ArrayLike], ArrayLike], x_min: float, x_max: float, label: Optional[str] = None, face_color: str | ArrayLike | NoneType | Inherit = INHERIT, edge_color: str | ArrayLike | NoneType | Inherit = INHERIT, color_map: str | Colormap | Inherit = INHERIT, color_map_range: Optional[tuple[float, float]] = None, show_color_bar: bool | Inherit = INHERIT, marker_size: int | Inherit = INHERIT, marker_edge_width: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, number_of_points: int = 30, ) -> Self: """ Creates a scatter plot from a function and a range of x values. Parameters ---------- func : Callable[[ArrayLike], ArrayLike] The function to be plotted. x_min, x_max : float The scatter plot will be created for x values between x_min and x_max. label : str, optional Label to be displayed in the legend. face_color : str or ArrayLike or None Face color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker faces are transparent. edge_color : str or ArrayLike or None Edge color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker edges are transparent. Default depends on the ``figure_style`` configuration. color_map : str or Colormap Color map used when ``face_color`` or ``edge_color`` is an array of intensity values. 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. marker_size : int Size of the points. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_style : str Style of the points. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. marker_edge_width: float Line width of the marker edges. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the scatter plot. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. number_of_points : int Number of points to be plotted. Defaults to 30. 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_1d.Scatter` object created from a function and a range of x values. """ x_data = np.linspace(x_min, x_max, number_of_points) y_data = func(x_data) return cls( x_data, y_data, label, face_color, edge_color, color_map, color_map_range, show_color_bar, marker_size, marker_edge_width, marker_style, alpha, )
@property def x_data(self) -> np.ndarray: 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) -> np.ndarray: return self._y_data @y_data.setter def y_data(self, y_data: ArrayLike) -> None: self._y_data = np.asarray(y_data) @property def x_error(self) -> np.ndarray | None: return self._x_error @x_error.setter def x_error(self, x_error: ArrayLike) -> None: self._x_error = np.asarray(x_error) @property def y_error(self) -> np.ndarray | None: return self._y_error @y_error.setter def y_error(self, y_error: ArrayLike) -> None: self._y_error = np.asarray(y_error) @property def label(self) -> str | None: return self._label @label.setter def label(self, label: str | None) -> None: self._label = label @property def face_color(self) -> str | ArrayLike: return self._face_color @face_color.setter def face_color(self, face_color: str | ArrayLike) -> None: self._face_color = face_color @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 @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 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_edge_width(self) -> float: return self._marker_edge_width @marker_edge_width.setter def marker_edge_width(self, value: float): self._marker_edge_width = value @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 alpha(self) -> float | Inherit: return self._alpha @alpha.setter def alpha(self, alpha: float | Inherit) -> None: self._alpha = alpha @property def show_errorbars(self) -> bool: return self._show_errorbars @show_errorbars.setter def show_errorbars(self, show_errorbars: bool) -> None: self._show_errorbars = show_errorbars @property def errorbars_line_width(self) -> float: return self._errorbars_line_width @errorbars_line_width.setter def errorbars_line_width(self, errorbars_line_width: float) -> None: self._errorbars_line_width = errorbars_line_width @property def cap_width(self) -> float: return self._cap_width @cap_width.setter def cap_width(self, cap_width: float) -> None: self._cap_width = cap_width @property def cap_thickness(self) -> float: return self._cap_thickness @cap_thickness.setter def cap_thickness(self, cap_thickness: float) -> None: self._cap_thickness = cap_thickness @property def errorbars_color(self) -> str: return self._errorbars_color @errorbars_color.setter def errorbars_color(self, errorbars_color: str) -> None: self._errorbars_color = errorbars_color @property def color_bar_params(self) -> dict: return self._color_bar_params def __eq__(self, other: Self) -> bool: """ Defines the equality between two scatters. """ return ( np.equal(self.x_data, other.x_data).all() and np.equal(self.y_data, other.y_data).all() ) def __add__(self, other: Self | float) -> Self: """ Defines the addition of two scatter plots or a scatter plot and a number. """ if isinstance(other, Scatter): try: assert np.array_equal(self._x_data, other._x_data) except AssertionError: raise ValueError( "Cannot add two scatter plots with different x values." ) new_y_data = self._y_data + other._y_data return Scatter(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data + other return Scatter(self._x_data, new_y_data) else: raise TypeError( "Can only add a scatter plot to another scatter plot or a number." ) def __sub__(self, other: Self | float) -> Self: """ Defines the subtraction of two scatter plots or a scatter plot and a number. """ if isinstance(other, Scatter): try: assert np.array_equal(self._x_data, other._x_data) except AssertionError: raise ValueError( "Cannot subtract two scatter plots with different x values." ) new_y_data = self._y_data - other._y_data return Scatter(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data - other return Scatter(self._x_data, new_y_data) else: raise TypeError( "Can only subtract a scatter plot from another scatter plot or a number." ) def __mul__(self, other: Self | float) -> Self: """ Defines the multiplication of two scatter plots or a scatter plot and a number. """ if isinstance(other, Scatter): try: assert np.array_equal(self._x_data, other._x_data) except AssertionError: raise ValueError( "Cannot multiply two scatter plots with different x values." ) new_y_data = self._y_data * other._y_data return Scatter(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data * other return Scatter(self._x_data, new_y_data) else: raise TypeError( "Can only multiply a scatter plot by another scatter plot or a number." ) def __truediv__(self, other: Self | float) -> Self: """ Defines the division of two scatter plots or a scatter plot and a number. """ if isinstance(other, Scatter): try: assert np.array_equal(self._x_data, other._x_data) except AssertionError: raise ValueError( "Cannot divide two scatter plots with different x values." ) new_y_data = self._y_data / other._y_data return Scatter(self._x_data, new_y_data) elif isinstance(other, (int, float)): new_y_data = self._y_data / other return Scatter(self._x_data, new_y_data) else: raise TypeError( "Can only divide a scatter plot by another scatter plot or a number." ) def __pow__(self, other: float) -> Self: """ Defines the power of a scatter plot to a number. """ if isinstance(other, (int, float)): new_y_data = self._y_data**other return Scatter(self._x_data, new_y_data) else: raise TypeError( "Can only raise a scatter plot to another scatter plot or a number." ) def __iter__(self): """ Defines the iteration of a scatter plot. Returns the y values. """ return iter(self._y_data) def __abs__(self) -> Self: """ Defines the absolute value of a scatter plot. """ new_y_data = np.abs(self._y_data) return Scatter(self._x_data, new_y_data)
[docs] def copy(self) -> Self: """ Returns a deep copy of the :class:`~graphinglib.data_plotting_1d.Scatter` object. """ return deepcopy(self)
[docs] def create_slice_x( self, x_min: float, x_max: float, label: Optional[str] = None, face_color: str | ArrayLike | NoneType | Inherit = INHERIT, edge_color: str | ArrayLike | NoneType | Inherit = INHERIT, color_map: str | Colormap | Inherit = INHERIT, color_map_range: Optional[tuple[float, float]] = None, show_color_bar: bool | Inherit = INHERIT, marker_size: float | Inherit = INHERIT, marker_edge_width: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a slice of the scatter plot between two x values. Parameters ---------- x_min, x_max : float The slice will be created between x_min and x_max. label : str, optional Label to be displayed in the legend. face_color : str or ArrayLike or None Face color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker faces are transparent. Default depends on the ``figure_style`` configuration. edge_color : str or ArrayLike or None Edge color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker edges are transparent. Default depends on the ``figure_style`` configuration. color_map : str or Colormap Color map used when ``face_color`` or ``edge_color`` is an array of intensity values. 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. marker_size : float Size of the points. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_edge_width: float Line width of the marker edges. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. marker_style : str Style of the points. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. alpha : float Opacities of the points. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the scatter plot (with all its parameters) will be returned with the slice applied. Any other parameters passed to this method will also be applied to the copied scatter plot. If ``False``, a new scatter plot will be created with the slice applied and the parameters passed to this method. 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 ------- :class:`~graphinglib.data_plotting_1d.Scatter` A new :class:`~graphinglib.data_plotting_1d.Scatter` object which is a slice of the original scatter plot. """ mask = (self._x_data >= x_min) & (self._x_data <= x_max) if copy_first: copy = self.copy() copy._x_data = self._x_data[mask] copy._y_data = self._y_data[mask] if label is not None: copy._label = label if face_color != INHERIT: copy._face_color = face_color if edge_color != INHERIT: copy._edge_color = edge_color if color_map != INHERIT: copy._color_map = color_map if color_map_range: copy._color_map_range = color_map_range if show_color_bar != INHERIT: copy._show_color_bar = show_color_bar if marker_size != INHERIT: copy._marker_size = marker_size if marker_edge_width != INHERIT: copy._marker_edge_width = marker_edge_width if marker_style != INHERIT: copy._marker_style = marker_style if alpha != INHERIT: copy._alpha = alpha return copy else: return Scatter( self._x_data[mask], self._y_data[mask], label, face_color, edge_color, color_map, color_map_range, show_color_bar, marker_size, marker_edge_width, marker_style, alpha, )
[docs] def create_slice_y( self, y_min: float, y_max: float, label: Optional[str] = None, face_color: str | ArrayLike | NoneType | Inherit = INHERIT, edge_color: str | ArrayLike | NoneType | Inherit = INHERIT, color_map: str | Colormap | Inherit = INHERIT, color_map_range: Optional[tuple[float, float]] = None, show_color_bar: bool | Inherit = INHERIT, marker_size: float | Inherit = INHERIT, marker_edge_width: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, copy_first: bool = False, ) -> Self: """ Creates a slice of the scatter plot between two y values. Parameters ---------- y_min, y_max : float The slice will be created between y_min and y_max. label : str, optional Label to be displayed in the legend. face_color : str or ArrayLike or None Face color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker faces are transparent. Default depends on the ``figure_style`` configuration. edge_color : str or ArrayLike or None Edge color of the points. If an array of intensities is provided, the values are mapped to the specified color map. If None, marker edges are transparent. Default depends on the ``figure_style`` configuration. color_map : str or Colormap Color map used when ``face_color`` or ``edge_color`` is an array of intensity values. 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. marker_size : float Size of the points. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_edge_width: float Line width of the marker edges. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. marker_style : str Style of the points. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. Default depends on the ``figure_style`` configuration. alpha : float Opacities of the points. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. copy_first : bool If ``True``, a copy of the scatter plot (with all its parameters) will be returned with the slice applied. Any other parameters passed to this method will also be applied to the copied scatter plot. If ``False``, a new scatter plot will be created with the slice applied and the parameters passed to this method. 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 ------- :class:`~graphinglib.data_plotting_1d.Scatter` A new :class:`~graphinglib.data_plotting_1d.Scatter` object which is a slice of the original scatter plot. """ mask = (self._y_data >= y_min) & (self._y_data <= y_max) if copy_first: copy = self.copy() copy._x_data = self._x_data[mask] copy._y_data = self._y_data[mask] if label is not None: copy._label = label if face_color != INHERIT: copy._face_color = face_color if edge_color != INHERIT: copy._edge_color = edge_color if color_map != INHERIT: copy._color_map = color_map if color_map_range: copy._color_map_range = color_map_range if show_color_bar != INHERIT: copy._show_color_bar = show_color_bar if marker_size != INHERIT: copy._marker_size = marker_size if marker_edge_width != INHERIT: copy._marker_edge_width = marker_edge_width if marker_style != INHERIT: copy._marker_style = marker_style if alpha != INHERIT: copy._alpha = alpha return copy else: return Scatter( self._x_data[mask], self._y_data[mask], label, face_color, edge_color, color_map, color_map_range, show_color_bar, marker_size, marker_edge_width, marker_style, alpha, )
[docs] def add_errorbars( self, x_error: Optional[ArrayLike] = None, y_error: Optional[ArrayLike] = None, cap_width: float | Inherit = INHERIT, errorbars_color: str | Inherit = INHERIT, errorbars_line_width: float | Inherit = INHERIT, cap_thickness: float | Inherit = INHERIT, ) -> None: """ Adds errorbars to the scatter plot. Parameters ---------- x_error, y_error : ArrayLike, optional Arrays of x and y errors. Use one or both. Values must be non-negative. cap_width : float Width of the errorbar caps. Typical range is ``0`` to ``10`` points. Default depends on the ``figure_style`` configuration. errorbars_color : str Color of the errorbars. ``"same as scatter"`` uses the scatter marker color. Default depends on the ``figure_style`` configuration. errorbars_line_width : float Width of the errorbars. Typical range is ``0.5`` to ``3`` points. Default depends on the ``figure_style`` configuration. cap_thickness : float Thickness of the errorbar caps. Typical range is ``0.5`` to ``3`` points. 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._show_errorbars = True if x_error is not None: self._x_error = np.array(x_error) if y_error is not None: self._y_error = np.array(y_error) self._errorbars_color = errorbars_color self._errorbars_line_width = errorbars_line_width self._cap_thickness = cap_thickness self._cap_width = cap_width
[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
[docs] def get_coordinates_at_x( self, x: float, interpolation_method: str = "linear", ) -> tuple[float, float]: """ Gets the coordinates of the point on the curve at a given x value. Parameters ---------- x : float The x value of the point. interpolation_method : str, The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". Returns ------- tuple[float, float] The coordinates of the point on the curve at the given x value. """ return ( x, float(interp1d(self._x_data, self._y_data, kind=interpolation_method)(x)), )
[docs] def create_point_at_x( self, x: float, interpolation_method: str = "linear", label: Optional[str] = None, face_color: str | Inherit = INHERIT, edge_color: str | Inherit = INHERIT, marker_size: float | Inherit = INHERIT, marker_edge_width: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, ) -> Point: """ Creates a Point on the curve at a given x value. Parameters ---------- x : float The x value of the point. interpolation_method : str, The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". label : str, optional Label to be displayed in the legend. face_color : str Face color of the point. Default depends on the ``figure_style`` configuration. edge_color : str Edge color of the point. Default depends on the ``figure_style`` configuration. marker_size : float Size of the point. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_edge_width : float Width of the point edge. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. marker_style : str Style of the point. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. 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. 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 ------- :class:`~graphinglib.graph_elements.Point` The Point on the curve at the given x value. """ point = Point( x, self.get_coordinates_at_x(x, interpolation_method)[1], label=label, face_color=face_color, edge_color=edge_color, marker_size=marker_size, marker_style=marker_style, edge_width=marker_edge_width, alpha=alpha, ) return point
[docs] def get_coordinates_at_y( self, y: float, interpolation_method: str = "linear", ) -> list[tuple[float, float]]: """ Gets the coordinates the curve at a given y value. Can return multiple coordinate pairs if the curve crosses the y value multiple times. Parameters ---------- y : float The y value of the point. interpolation_method : str, The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". Returns ------- list[tuple[float, float]] The coordinates of the points on the curve at the given y value. """ xs = self._x_data ys = self._y_data assert isinstance(xs, np.ndarray) and isinstance(ys, np.ndarray) crossings = np.where(np.diff(np.sign(ys - y)))[0] x_vals: list[float] = [] for cross in crossings: x1, x2 = xs[cross], xs[cross + 1] y1, y2 = ys[cross], ys[cross + 1] f = interp1d([y1, y2], [x1, x2], kind=interpolation_method) x_val = f(y) x_vals.append(float(x_val)) points = [(x_val, y) for x_val in x_vals] return points
[docs] def create_points_at_y( self, y: float, interpolation_method: str = "linear", label: Optional[str] = None, face_color: str | Inherit = INHERIT, edge_color: str | Inherit = INHERIT, marker_size: float | Inherit = INHERIT, marker_edge_width: float | Inherit = INHERIT, marker_style: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, ) -> list[Point]: """ Creates the Points on the curve at a given y value. Can return multiple Points if the curve crosses the y value multiple times. Parameters ---------- y : float The y value of the desired points. interpolation_method : str The type of interpolation to be used, as defined in ``scipy.interpolate.interp1d``. .. seealso:: `scipy.interpolate.interp1d <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html>`_ Defaults to "linear". label : str, optional Label to be displayed in the legend. face_color : str Face color of the point. Default depends on the ``figure_style`` configuration. edge_color : str Edge color of the point. Default depends on the ``figure_style`` configuration. marker_size : float Size of the point. Typical range is ``10`` to ``100``. Default depends on the ``figure_style`` configuration. marker_edge_width : float Width of the point edge. Typical range is ``0`` to ``3`` points. Default depends on the ``figure_style`` configuration. marker_style : str Style of the point. Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``, ``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``, ``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``. 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. 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 ------- list[:class:`~graphinglib.graph_elements.Point`] The Point objects on the curve at the given y value. """ coords = self.get_coordinates_at_y(y, interpolation_method) points = [ Point( coord[0], coord[1], label=label, face_color=face_color, edge_color=edge_color, marker_size=marker_size, marker_style=marker_style, edge_width=marker_edge_width, alpha=alpha, ) for coord in coords ] return points
[docs] def to_desmos(self, decimal_precision: int = 2, to_clipboard: bool = False) -> str: """ Gives the data points in a Desmos-readable format. The outputted string can then be pasted into a single Desmos cell and the object's data will be displayed. Parameters ---------- decimal_precision : int, optional Specifies the number of decimals of the formatted points. Defaults to 2. to_clipboard : bool, optional Specifies whether the points should be directly copied to the user's clipboard in addition to being returned as a string. Defaults to False. Returns ------- formatted points : str A list of tuples representing every data point. """ formatted_points = super().to_desmos( self._x_data, self._y_data, decimal_precision ) if to_clipboard: copy_to_clipboard(formatted_points) return formatted_points
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None: """ Plots the element in the specified axes. """ # Check that either face color or edge color is not None if self._face_color is None and self._edge_color is None: raise ValueError( "Both face color and edge color cannot be None. Please set at least one of them to a valid color." ) # Check that either face color or edge color is not a list/array/tuple of intensities if isinstance(self._face_color, (list, tuple, np.ndarray)) and isinstance( self._edge_color, (list, tuple, np.ndarray) ): # If they're 3 or 4 element arrays, assume they're RGB or RGBA values if len(self._face_color) in [3, 4] or len(self._edge_color) in [3, 4]: pass else: raise ValueError( "Both face color and edge color cannot be lists/arrays/tuples of intensities or colors. " "Please set at least one of them to a valid color or set one of them to None." ) # Convert face color to matplotlib notation if self._face_color is None: # Set to transparent mpl_face_color = "none" elif is_inherit(self._face_color): # Use color cycle (figure uses a matplotlib style) mpl_face_color = None elif isinstance(self._face_color, str) and self._face_color == "color cycle": # Use color cycle mpl_face_color = None else: # Use specified color mpl_face_color = self._face_color # Convert edge color to matplotlib notation if self._edge_color is None: # Set to transparent mpl_edge_color = "none" elif is_inherit(self._edge_color): # Use color cycle (figure uses a matplotlib style) mpl_edge_color = None elif isinstance(self._edge_color, str) and self._edge_color == "color cycle": # Use color cycle mpl_edge_color = kwargs["cycle_color"] else: # Use specified color mpl_edge_color = self._edge_color # Check whether to use color map (one of the colors is an array of intensities) if isinstance(self._face_color, (list, tuple, np.ndarray)): if all(isinstance(i, (int, float)) for i in self._face_color): color_map = plt.get_cmap(self._color_map) # Sets the data range that the color map will cover. if self._color_map_range: norm = Normalize( vmin=min(self._color_map_range), vmax=max(self._color_map_range) ) else: # Calculate from the array of intensities norm = Normalize( vmin=min(self._face_color), vmax=max(self._face_color) ) mpl_face_color = [color_map(norm(i)) for i in self._face_color] elif isinstance(self._edge_color, (list, tuple, np.ndarray)): if all(isinstance(i, (int, float)) for i in self._edge_color): color_map = plt.get_cmap(self._color_map) # Sets the data range that the color map will cover. if self._color_map_range: norm = Normalize( vmin=min(self._color_map_range), vmax=max(self._color_map_range) ) else: # Calculate from the array of intensities norm = Normalize( vmin=min(self._edge_color), vmax=max(self._edge_color) ) mpl_edge_color = [color_map(norm(i)) for i in self._edge_color] params = { "s": self._marker_size, "marker": self._marker_style, "linewidth": self._marker_edge_width, "alpha": self._alpha, } params = {k: v for k, v in params.items() if v != INHERIT} params["facecolors"] = mpl_face_color params["edgecolors"] = mpl_edge_color self.handle = axes.scatter( self._x_data, self._y_data, label=self._label, zorder=z_order, **params, ) if self._show_errorbars: # Convert errorbars color to matplotlib notation if self._errorbars_color is None: raise ValueError( "Errorbars color cannot be None. Please set the errorbars color to a valid color." ) elif is_inherit(self._errorbars_color): # Use color cycle mpl_errorbars_color = None elif ( isinstance(self._errorbars_color, str) and self._errorbars_color == "same as scatter" ): marker_edge_color = self.handle.get_edgecolor() marker_face_color = self.handle.get_facecolor() if is_color_like(marker_edge_color): mpl_errorbars_color = marker_edge_color elif is_color_like(marker_face_color): mpl_errorbars_color = marker_face_color else: ax_face_color = plt.rcParams["axes.facecolor"] mpl_errorbars_color = get_contrasting_shade(ax_face_color) elif isinstance(self._errorbars_color, str): # Use specified color mpl_errorbars_color = self._errorbars_color else: raise ValueError("Errorbars color must be a string.") errorbar_params = { "markerfacecolor": None, "markeredgecolor": None, "elinewidth": self._errorbars_line_width, "capsize": self._cap_width, "capthick": self._cap_thickness, "linestyle": "none", } errorbar_params = {k: v for k, v in errorbar_params.items() if v != INHERIT} errorbar_params["ecolor"] = mpl_errorbars_color self.errorbars_handle = axes.errorbar( self._x_data, self._y_data, xerr=self._x_error, yerr=self._y_error, zorder=z_order - 1, **errorbar_params, ) if ( self._show_color_bar and self._face_color is not None and not is_inherit(self._face_color) and not isinstance(self.face_color, str) ): # Create color bar from face color intensities color_map_name = ( plt.rcParams["image.cmap"] if is_inherit(self._color_map) else self._color_map ) color_map = plt.get_cmap(color_map_name) # Sets the data range that the color map on the color bar will cover. # Otherwise, it will be calculated from the array of intensities. if self._color_map_range: norm = Normalize( vmin=min(self._color_map_range), vmax=max(self._color_map_range) ) else: norm = Normalize(vmin=min(self._face_color), vmax=max(self._face_color)) sm = plt.cm.ScalarMappable(cmap=color_map, norm=norm) sm.set_array([]) plt.colorbar(sm, ax=axes, **self._color_bar_params) if ( self._show_color_bar and self._edge_color is not None and not is_inherit(self._edge_color) and not isinstance(self.edge_color, str) ): # Create color bar from edge color intensities color_map_name = ( plt.rcParams["image.cmap"] if is_inherit(self._color_map) else self._color_map ) color_map = plt.get_cmap(color_map_name) # Sets the data range that the color map on the color bar will cover. # Otherwise, it will be calculated from the array of intensities. if self._color_map_range: norm = Normalize( vmin=min(self._color_map_range), vmax=max(self._color_map_range) ) else: norm = Normalize(vmin=min(self._edge_color), vmax=max(self._edge_color)) sm = plt.cm.ScalarMappable(cmap=color_map, norm=norm) sm.set_array([]) plt.colorbar(sm, ax=axes, **self._color_bar_params)
[docs] @dataclass class Histogram(Plottable1D): """ This class implements a general histogram. Parameters ---------- data : ArrayLike Array of values to be plotted. bins : int | ArrayLike If `bins` is an integer, it defines the number of equal_width bins to be used in the histogram. If `bins` is an array, it defines the bin edges to be used in the histogram, including the left edge of the first bin and the right edge of the last bin. label : str, optional Label to be displayed in the legend. face_color : str Face color of the histogram. Default depends on the ``figure_style`` configuration. edge_color : str Edge color of the histogram. Default depends on the ``figure_style`` configuration. hist_type : str Type of the histogram. Values are ``"bar"``, ``"barstacked"``, ``"step"``, and ``"stepfilled"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the histogram. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. line_width : float Width of the histogram edge. Typical range is ``0.5`` to ``3`` points. Default depends on the ``figure_style`` configuration. normalize : bool Whether or not to normalize the histogram. Default depends on the ``figure_style`` configuration. orientation: str Whether to plot the histogram on x-axis or on y-axis. Values are ``"vertical"`` and ``"horizontal"``. Default depends on the ``figure_style`` configuration. show_params : bool Whether or not to show the mean and standard deviation of the data. 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, data: ArrayLike, bins: int, label: Optional[str] = None, face_color: str | Inherit = INHERIT, edge_color: str | Inherit = INHERIT, hist_type: str | Inherit = INHERIT, alpha: float | Inherit = INHERIT, line_width: float | Inherit = INHERIT, normalize: bool | Inherit = INHERIT, orientation: str | Inherit = INHERIT, show_params: bool | Inherit = INHERIT, ) -> None: """ This class implements a general histogram. Parameters ---------- data : ArrayLike Array of values to be plotted. bins : int | ArrayLike If `bins` is an integer, it defines the number of equal_width bins to be used in the histogram. If `bins` is an array, it defines the bin edges to be used in the histogram, including the left edge of the first bin and the right edge of the last bin. label : str, optional Label to be displayed in the legend. face_color : str Face color of the histogram. Default depends on the ``figure_style`` configuration. edge_color : str Edge color of the histogram. Default depends on the ``figure_style`` configuration. hist_type : str Type of the histogram. Values are ``"bar"``, ``"barstacked"``, ``"step"``, and ``"stepfilled"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the histogram. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. line_width : float Width of the histogram edge. Typical range is ``0.5`` to ``3`` points. Default depends on the ``figure_style`` configuration. normalize : bool Whether or not to normalize the histogram. Default depends on the ``figure_style`` configuration. orientation: str Whether to plot the histogram on x-axis or on y-axis. Values are ``"vertical"`` and ``"horizontal"``. Default depends on the ``figure_style`` configuration. show_params : bool Whether or not to show the mean and standard deviation of the data. 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._bins = bins self._label = label self._face_color = face_color self._edge_color = edge_color self._hist_type = hist_type self._alpha = alpha self._line_width = line_width self._normalize = normalize self._orientation = orientation self._show_params = show_params self.data = np.asarray(data) self._show_pdf = False self._pdf_type = None self._pdf_show_mean = None self._pdf_show_std = None self._pdf_curve_color = None self._pdf_mean_color = None self._pdf_std_color = None
[docs] @classmethod def from_fit_residuals( cls, fit: Fit, bins: int, label: Optional[str] = None, face_color: str | Inherit = INHERIT, edge_color: str | Inherit = INHERIT, hist_type: str | Inherit = INHERIT, alpha: int | Inherit = INHERIT, line_width: int | Inherit = INHERIT, normalize: bool | Inherit = INHERIT, orientation: str | Inherit = INHERIT, show_params: bool | Inherit = INHERIT, ) -> Self: """ Calculates the residuals of a fit and plots them as a histogram. Parameters ---------- fit : Fit The fit from which the residuals are to be calculated. bins : int | ArrayLike If `bins` is an integer, it defines the number of equal_width bins to be used in the histogram. If `bins` is an array, it defines the bin edges to be used in the histogram, including the left edge of the first bin and the right edge of the last bin. label : str, optional Label to be displayed in the legend. face_color : str Face color of the histogram. Default depends on the ``figure_style`` configuration. edge_color : str Edge color of the histogram. Default depends on the ``figure_style`` configuration. hist_type : str Type of the histogram. Values are ``"bar"``, ``"barstacked"``, ``"step"``, and ``"stepfilled"``. Default depends on the ``figure_style`` configuration. alpha : float Opacity of the histogram. Range is ``0`` (transparent) to ``1`` (opaque). Default depends on the ``figure_style`` configuration. line_width : float Width of the histogram edge. Typical range is ``0.5`` to ``3`` points. Default depends on the ``figure_style`` configuration. normalize : bool Whether or not to normalize the histogram. Default depends on the ``figure_style`` configuration. orientation: str Whether to plot the histogram on x-axis or on y-axis. Values are ``"vertical"`` and ``"horizontal"``. Default depends on the ``figure_style`` configuration. show_params : bool Whether or not to show the mean and standard deviation of the data. 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_1d.Histogram` object created from the residuals of a fit. """ residuals = fit.get_residuals() return cls( residuals, bins, label, face_color, edge_color, hist_type, alpha, line_width, normalize, orientation, show_params, )
@property def data(self) -> np.ndarray: return self._data @data.setter def data(self, data: ArrayLike) -> None: self._data = np.array(data) self._mean = np.mean(self._data) self._standard_deviation = np.std(self._data) _parameters = np.histogram(self._data, bins=self._bins, density=self._normalize) self._bin_heights, bin_edges = _parameters[0], _parameters[1] bin_width = bin_edges[1] - bin_edges[0] bin_centers = bin_edges[1:] - bin_width / 2 self._bin_width = bin_width self._bin_centers = bin_centers self._bin_edges = bin_edges @property def bins(self) -> int: return self._bins @bins.setter def bins(self, bins: int) -> None: self._bins = bins _parameters = np.histogram(self._data, bins=self._bins, density=self._normalize) self._bin_heights, bin_edges = _parameters[0], _parameters[1] bin_width = bin_edges[1] - bin_edges[0] bin_centers = bin_edges[1:] - bin_width / 2 self._bin_width = bin_width self._bin_centers = bin_centers self._bin_edges = bin_edges @property def label(self) -> str: return self._get_label() @label.setter def label(self, label: str) -> None: self._label = label @property def face_color(self) -> str: 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: return self._edge_color @edge_color.setter def edge_color(self, edge_color: str) -> None: self._edge_color = edge_color @property def hist_type(self) -> str: return self._hist_type @hist_type.setter def hist_type(self, hist_type: str) -> None: self._hist_type = hist_type @property def alpha(self) -> float: return self._alpha @alpha.setter def alpha(self, alpha: float) -> None: self._alpha = alpha @property def line_width(self) -> float: return self._line_width @line_width.setter def line_width(self, line_width: float) -> None: self._line_width = line_width @property def normalize(self) -> bool: return self._normalize @normalize.setter def normalize(self, normalize: bool) -> None: self._normalize = normalize @property def orientation(self) -> str: return self._orientation @orientation.setter def orientation(self, orientation: str) -> None: self._orientation = orientation @property def show_params(self) -> bool: return self._show_params @show_params.setter def show_params(self, show_params: bool) -> None: self._show_params = show_params @property def mean(self) -> float: return self._mean @property def standard_deviation(self) -> float: return self._standard_deviation @property def parameters(self) -> tuple[float, float]: return self._mean, self._standard_deviation @property def bin_heights(self) -> np.ndarray: return self._bin_heights @property def bin_centers(self) -> np.ndarray: return self._bin_centers @property def bin_edges(self) -> np.ndarray: return self._bin_edges def __eq__(self, other: Self) -> bool: """ Defines the equality between two histograms. """ return ( np.equal(self.bin_heights, other.bin_heights).all() and np.equal(self.bin_centers, other.bin_centers).all() ) def _get_label(self) -> None: """ Gives the label of the histogram (with or without parameters). """ lab = self._label if lab and self._show_params: lab += ( " :\n" + rf"$\mu$ = {0 if abs(self._mean) < 1e-3 else self._mean:.3f}, $\sigma$ = {self._standard_deviation:.3f}" ) elif self._show_params: lab = rf"$\mu$ = {0 if abs(self._mean) < 1e-3 else self._mean:.3f}, $\sigma$ = {self._standard_deviation:.3f}" return lab
[docs] def copy(self) -> Self: """ Returns a deep copy of the :class:`~graphinglib.data_plotting_1d.Histogram` object. """ return deepcopy(self)
def _normal_normalized(self, x: ArrayLike) -> ArrayLike: """ Calculates the normalized gaussian curve from the mean and standard deviation of the data. Parameters ---------- x : ArrayLike The x values at which the gaussian curve is to be calculated. Returns ------- The corresponding array of y values of the gaussian curve. """ x = np.array(x) return (1 / (self._standard_deviation * np.sqrt(2 * np.pi))) * np.exp( -0.5 * (((x - self._mean) / self._standard_deviation) ** 2) ) def _normal_not_normalized(self, x: ArrayLike) -> ArrayLike: """ Calculates the (not normalized) gaussian curve from the mean and standard deviation of the data. Parameters ---------- x : ArrayLike The x values at which the gaussian curve is to be calculated. Returns ------- The corresponding array of y values of the gaussian curve. """ x = np.array(x) return sum(self._bin_heights) * self._bin_width * self._normal_normalized(x)
[docs] def add_pdf( self, type: str = "normal", show_mean: bool | Inherit = INHERIT, show_std: bool | Inherit = INHERIT, curve_color: str | Inherit = INHERIT, mean_color: str | Inherit = INHERIT, std_color: str | Inherit = INHERIT, ) -> None: """ Shows the probability density function of the histogram. Parameters ---------- type : str The type of probability density function to be shown. Currently only ``"normal"`` is supported. Defaults to "normal". show_mean : bool Whether or not to show the mean of the data. Default depends on the ``figure_style`` configuration. show_std : bool Whether or not to show the standard deviation of the data. Default depends on the ``figure_style`` configuration. curve_color : str Color of the probability density function curve. Default depends on the ``figure_style`` configuration. mean_color : str Color of the mean line. Default depends on the ``figure_style`` configuration. std_color : str Color of the standard deviation lines. 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)``). """ if type != "normal": raise ValueError("Currently, only 'normal' distribution is supported.") self._show_pdf = True self._pdf_type = type self._pdf_show_mean = show_mean self._pdf_show_std = show_std self._pdf_curve_color = curve_color self._pdf_mean_color = mean_color self._pdf_std_color = std_color
[docs] def to_desmos(self, decimal_precision: int = 2, to_clipboard: bool = False) -> str: """ Gives every bin's upper center in a Desmos-readable format. The outputted string can then be pasted into a single Desmos cell and the object's data will be displayed. Parameters ---------- decimal_precision : int, optional Specifies the number of decimals of the formatted points. Defaults to 2. to_clipboard : bool, optional Specifies whether the points should be directly copied to the user's clipboard in addition to being returned as a string. Defaults to False. Returns ------- formatted points : str A list of tuples representing every data point. """ formatted_points = super().to_desmos( self.bin_centers, self.bin_heights, decimal_precision ) if to_clipboard: copy_to_clipboard(formatted_points) return formatted_points
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None: """ Plots the element in the specified axes. """ params = { "facecolor": ( to_rgba(self._face_color, self._alpha) if self._face_color != INHERIT and self._alpha != INHERIT else INHERIT ), "edgecolor": ( to_rgba(self._edge_color, 1) if self._edge_color != INHERIT else self._edge_color ), "linewidth": self._line_width, } params = {k: v for k, v in params.items() if v != INHERIT} self.handle = Polygon( np.array([[0, 2, 2, 3, 3, 1, 1, 0, 0], [0, 0, 1, 1, 2, 2, 3, 3, 0]]).T, **params, ) params = { "facecolor": ( to_rgba(self._face_color, self._alpha) if self._face_color != INHERIT and self._alpha != INHERIT else INHERIT ), "edgecolor": ( to_rgba(self._edge_color, 1) if self._edge_color != INHERIT else self._edge_color ), "histtype": self._hist_type, "linewidth": self._line_width, "density": self._normalize, "orientation": self._orientation, } params = {k: v for k, v in params.items() if v != INHERIT} axes.hist( self._data, bins=self._bins, label=self.label, # uses the get_label() method zorder=z_order - 1, **params, ) if self._show_pdf: normal = ( self._normal_normalized if self._normalize else self._normal_not_normalized ) num_of_points = 500 x_data = np.linspace(self._bin_edges[0], self._bin_edges[-1], num_of_points) y_data = normal(x_data) params = { "color": self._pdf_curve_color, } params = {k: v for k, v in params.items() if v != INHERIT} # Plots pdf on the y-axis if "orientation" is "horizontal". if self._orientation != "vertical": axes.plot( y_data, x_data, zorder=z_order, **params, ) else: axes.plot( x_data, y_data, zorder=z_order, **params, ) curve_max_y = normal(self._mean) curve_std_y = normal(self._mean + self._standard_deviation) if self._pdf_show_std: params = {} if self._pdf_std_color != INHERIT: params["colors"] = [self._pdf_std_color, self._pdf_std_color] # Plots std on the y-axis if "orientation" is "horizontal". if self._orientation != "vertical": plt.hlines( [ self._mean - self._standard_deviation, self._mean + self._standard_deviation, ], [0, 0], [curve_std_y, curve_std_y], linestyles=["dashed"], zorder=z_order - 1, **params, ) else: plt.vlines( [ self._mean - self._standard_deviation, self._mean + self._standard_deviation, ], [0, 0], [curve_std_y, curve_std_y], linestyles=["dashed"], zorder=z_order - 1, **params, ) if self._pdf_show_mean: params = {} if self._pdf_mean_color != INHERIT: params["colors"] = [self._pdf_mean_color] # Plots std on the y-axis if "orientation" is "horizontal". if self._orientation != "vertical": plt.hlines( self._mean, 0, curve_max_y, linestyles=["dashed"], zorder=z_order - 1, **params, ) else: plt.vlines( self._mean, 0, curve_max_y, linestyles=["dashed"], zorder=z_order - 1, **params, )