Source code for pysatl_cpd.analysis.visualization.abstracts.ivisual_component

# -*- coding: ascii -*-
"""
Visualization component interfaces.

This module defines the abstract source class for composable visualizers
components that draw specific elements (change points, segments, annotations)
onto a single subplot. Components are backend-agnostic and provide separate
implementations for Matplotlib and Plotly backends.

Components must support optional legend entries with the ability to toggle
visibility interactively in Plotly through legend groups.
"""

__author__ = "Danil Totmyanin"
__copyright__ = "Copyright (c) 2026 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from abc import ABC, abstractmethod
from typing import overload

from pysatl_cpd.analysis.visualization.typedefs import DrawBackend, Figure, GoAxes, GoFigure, PltAxes, PltFigure
from pysatl_cpd.analysis.visualization.utils import normalize_backend


[docs] class IVisualComponent(ABC): """ Base interface for composable visualizers components. Components are responsible for drawing specific elements (e.g., change points, segments, annotations) onto a single subplot. They are backend-agnostic and implement both Matplotlib and Plotly drawing methods. Parameters ---------- backend The backend to use for drawing operations. """
[docs] def __init__(self, backend: DrawBackend | str) -> None: self._backend = normalize_backend(backend)
@property def backend(self) -> DrawBackend: """Return the current drawing backend.""" return self._backend @backend.setter def backend(self, value: DrawBackend | str) -> None: self._backend = normalize_backend(value) @overload def draw(self, figure: PltFigure, axes: PltAxes, add_legend: bool = False) -> None: """ Draw component on a Matplotlib axes. Parameters ---------- figure The Matplotlib figure containing the axes. axes The Matplotlib axes to draw on. add_legend Whether to add legend entry for this component. """ @overload def draw(self, figure: GoFigure, axes: GoAxes, add_legend: bool = False) -> None: """ Draw component on a Plotly subplot. Parameters ---------- figure The Plotly figure containing the subplot. axes The subplot position (row, col) to draw on. add_legend Whether to display legend entry for this component. """
[docs] def draw(self, figure: Figure, axes: GoAxes | PltAxes, add_legend: bool = False) -> None: """ Draw component on a subplot. Parameters ---------- figure The figure containing the subplot (Matplotlib or Plotly). axes The subplot data to draw on. add_legend Whether to add (for Matplotlib) or display (for Plotly) legend entry for this component. Raises ------ TypeError If the figure or axes type does not match the current backend. ValueError If drawing with legend when label is not set, or if an unsupported backend is provided. """ style = getattr(self, "_style", None) if add_legend and isinstance(style, dict) and style.get("legend", True) and style.get("label") is None: raise ValueError("Can not draw with legend: label is set to None") if self._backend == DrawBackend.MATPLOTLIB: if not isinstance(figure, PltFigure.__value__): raise TypeError(f"Expected PltFigure for Matplotlib backend, got {type(figure)}") if not isinstance(axes, PltAxes.__value__): raise TypeError(f"Expected PltAxes for Matplotlib backend, got {type(axes)}") self._draw_matplotlib(figure, axes, add_legend) elif self._backend == DrawBackend.PLOTLY: if not isinstance(figure, GoFigure.__value__): raise TypeError(f"Expected GoFigure for Plotly backend, got {type(figure)}") if not ( isinstance(axes, tuple) and (len(axes) == 2) and isinstance(axes[0], int) and isinstance(axes[1], int) ): raise TypeError(f"Expected tuple of ints for Plotly backend, got {axes}") self._draw_plotly(figure, axes, add_legend) else: raise ValueError(f"Unsupported backend: {self.backend}")
@abstractmethod def _draw_matplotlib(self, figure: PltFigure, axes: PltAxes, add_legend: bool = False) -> None: # pragma: no cover """ Backend-specific drawing implementation for Matplotlib. """ raise NotImplementedError @abstractmethod def _draw_plotly(self, figure: GoFigure, axes: GoAxes, add_legend: bool = False) -> None: # pragma: no cover """ Backend-specific drawing implementation for Plotly. """ raise NotImplementedError