Source code for pysatl_cpd.analysis.visualization.timeseries.univariate_timeseries_visualizer

# -*- coding: ascii -*-
"""
Univariate time series visualizer implementation.

This module provides a visualizer for rendering univariate time series data
with change point annotations, period fills, and ground truth markers.
"""

from __future__ import annotations

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


from typing import Self, Unpack

import plotly.graph_objs as go

from pysatl_cpd.analysis.visualization.abstracts import (
    ITimeseriesVisualizer,
)
from pysatl_cpd.analysis.visualization.specs import LineSpec, PlotSpec
from pysatl_cpd.analysis.visualization.typedefs import (
    DrawBackend,
    GoAxMapping,
    GoFigure,
    PltAxMapping,
    PltFigure,
)
from pysatl_cpd.analysis.visualization.utils import (
    apply_matplotlib_plot_spec,
    apply_plotly_plot_spec,
    get_matplotlib_legend_label,
    get_plotly_legend_kwargs,
    line_spec_to_mpl_kwargs,
    line_spec_to_plotly_trace_kwargs,
)
from pysatl_cpd.data import DataProvider
from pysatl_cpd.data.typedefs import TimeseriesAnnotation
from pysatl_cpd.typedefs import Number

# Plotly layout constants
PLOTLY_GRID_COLOR = "lightgray"
PLOTLY_GRID_WIDTH = 1
PLOTLY_TITLE_X = 0.5
PLOTLY_TITLE_Y = 0.95

# Matplotlib constants
MPL_GRID_ALPHA = 0.3


[docs] class UnivariateTimeseriesVisualizer(ITimeseriesVisualizer[DataProvider[Number, TimeseriesAnnotation]]): """ Visualizer for univariate time series with change point annotations. This visualizer renders the original time series data with optional detected change points, forced change points, ground truth markers, learning periods, skip periods, and margin windows. Parameters ---------- backend Plotting backend to use for rendering. """
[docs] def __init__(self, backend: DrawBackend) -> None: super().__init__(backend) self._data_provider: DataProvider[Number, TimeseriesAnnotation] | None = None # Options storage with defaults self._plot_opts: PlotSpec = { "xlabel": "Time Index", "ylabel": "Value", "grid": True, "title": None, } self._draw_opts: LineSpec = { "color": "black", "linewidth": 1.5, "alpha": 1.0, "label": "Time Series", "legend": True, }
[docs] def set_data_provider(self, data_provider: DataProvider[Number, TimeseriesAnnotation]) -> Self: """Set the data provider containing the time series observations. Parameters ---------- data_provider Provider with univariate numeric observations. Returns ------- Self """ self._data_provider = data_provider return self
[docs] def set_plot_opts(self, **options: Unpack[PlotSpec]) -> Self: """Set general plot options for time series subplot. Parameters ---------- **options :xlabel: X-axis label. :ylabel: Y-axis label. :grid: Show grid lines. :title: Subplot title. Returns ------- Self """ self._plot_opts.update(options) return self
[docs] def set_draw_opts(self, **options: Unpack[LineSpec]) -> Self: """Set drawing options for time series line. Parameters ---------- **options :color: Line colour. :linewidth: Line width. :alpha: Line opacity. :label: Legend label. :legend: Show legend entry. Returns ------- Self """ self._draw_opts.update(options) return self
@property def axes(self) -> set[str]: """Subplot names required by this visualizer. Returns ------- set[str] """ return {"timeseries"} def _draw_matplotlib(self, figure: PltFigure, axes: PltAxMapping) -> PltFigure: """Draw the time series on a Matplotlib axes. Parameters ---------- figure Matplotlib figure. axes Axes mapping containing ``"timeseries"``. Returns ------- PltFigure """ if "timeseries" not in axes: return figure ax = axes["timeseries"] # Draw time series data if self._data_provider is not None: time_points = list(range(len(self._data_provider))) values = list(self._data_provider) ax.plot( time_points, values, label=get_matplotlib_legend_label(self._draw_opts), **line_spec_to_mpl_kwargs(self._draw_opts), ) apply_matplotlib_plot_spec(ax, self._plot_opts, grid_alpha=MPL_GRID_ALPHA) return figure def _draw_plotly(self, figure: GoFigure, axes: GoAxMapping) -> GoFigure: """Draw the time series on a Plotly subplot. Parameters ---------- figure Plotly figure. axes Axes mapping containing ``"timeseries"``. Returns ------- GoFigure """ if "timeseries" not in axes: return figure row, col = axes["timeseries"] # Draw time series data if self._data_provider is not None: time_points = list(range(len(self._data_provider))) values = list(self._data_provider) figure.add_trace( go.Scatter( x=time_points, y=values, mode="lines", **line_spec_to_plotly_trace_kwargs(self._draw_opts), **get_plotly_legend_kwargs(self._draw_opts), ), row=row, col=col, ) apply_plotly_plot_spec( figure, row, col, self._plot_opts, grid_width=PLOTLY_GRID_WIDTH, grid_color=PLOTLY_GRID_COLOR, ) return figure