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

# -*- coding: ascii -*-
"""
Visualization interfaces for change-point detection results.

This module defines abstract source classes for visualizers that render
time series data, detection traces, and algorithm performance metrics
using either Matplotlib or Plotly backends.
"""

__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 (
    AxMapping,
    DrawBackend,
    Figure,
    GoAxMapping,
    GoFigure,
    PltAxMapping,
    PltFigure,
)
from pysatl_cpd.analysis.visualization.utils import (
    is_matplotlib_figure,
    is_matplotlib_mapping,
    is_plotly_figure,
    is_plotly_mapping,
    normalize_backend,
)


[docs] class IVisualizer(ABC): """ Abstract source class for all visualizers. This interface defines the contract for visualizers that render data onto specific subplots within a figure. Each visualizer declares which subplot axes it requires and provides backend-specific drawing methods. The visualizer pattern enables composable figure construction, where a coordinator creates a figure with named subplots, and each visualizer draws its content onto its designated axes. Parameters ---------- backend Plotting backend to use for rendering (MATPLOTLIB or PLOTLY). """
[docs] def __init__(self, backend: DrawBackend | str) -> None: self._backend = normalize_backend(backend)
@property def backend(self) -> DrawBackend: """ Return the plotting backend used by this visualizer. Returns ------- DrawBackend Current backend (MATPLOTLIB or PLOTLY). """ return self._backend @backend.setter def backend(self, value: DrawBackend | str) -> None: """Set the plotting backend. Parameters ---------- value Backend name (``MATPLOTLIB`` or ``PLOTLY``). """ self._backend = normalize_backend(value) @property @abstractmethod def axes(self) -> set[str]: # pragma: no cover """ Declare the subplot names required by this visualizer. Returns ------- set[str] Set of subplot identifiers that this visualizer will draw onto. These names must correspond to axes provided in the AxMapping when the draw method is called. Raises ------ NotImplementedError Subclasses must implement this property. """ raise NotImplementedError @overload def draw(self, *, figure: PltFigure, axes: PltAxMapping) -> PltFigure: ... # pragma: no cover @overload def draw(self, *, figure: GoFigure, axes: GoAxMapping) -> GoFigure: ... # pragma: no cover
[docs] def draw(self, *, figure: Figure, axes: AxMapping) -> Figure: """ Draw content onto the specified axes. This method dispatches to the appropriate backend-specific implementation based on the backend argument. The figure is inferred from the axes objects (for Matplotlib) or created automatically (for Plotly). Parameters ---------- figure The figure to draw onto (Matplotlib or Plotly). axes Mapping from subplot names to their axes objects or positions. Returns ------- Figure The modified figure object. Raises ------ TypeError If the figure or axes mapping type does not match the backend. ValueError If the backend is not supported. """ if self.backend == DrawBackend.PLOTLY: if not is_plotly_figure(figure): raise TypeError(f"Expected GoFigure for Plotly backend, got {type(figure)}") if not is_plotly_mapping(axes): raise TypeError(f"Expected Plotly axes mapping for Plotly backend, got {type(axes)}") return self._draw_plotly(figure, axes) if self.backend == DrawBackend.MATPLOTLIB: if not is_matplotlib_figure(figure): raise TypeError(f"Expected PltFigure for Matplotlib backend, got {type(figure)}") if not is_matplotlib_mapping(axes): raise TypeError(f"Expected Matplotlib axes mapping for Matplotlib backend, got {type(axes)}") return self._draw_matplotlib(figure, axes) # This line should never be reached due to validation in __init__ raise ValueError(f"Unsupported backend: {self.backend}")
@abstractmethod def _draw_matplotlib(self, figure: PltFigure, axes: PltAxMapping) -> PltFigure: # pragma: no cover """ Draw using Matplotlib backend. Parameters ---------- figure The Matplotlib figure to draw onto. axes Mapping from subplot names to Matplotlib axes objects. Returns ------- PltFigure The figure containing the drawn axes. Raises ------ NotImplementedError Subclasses must implement this method. """ raise NotImplementedError @abstractmethod def _draw_plotly(self, figure: GoFigure, axes: GoAxMapping) -> GoFigure: # pragma: no cover """ Draw using Plotly backend. Parameters ---------- figure The Plotly figure to draw onto. axes Mapping from subplot names to Plotly subplot positions. Returns ------- GoFigure The modified Plotly figure. Raises ------ NotImplementedError Subclasses must implement this method. """ raise NotImplementedError