Source code for pysatl_cpd.analysis.visualization.components.vert_fill

# -*- coding: ascii -*-
"""
Vertical fill visualizers component.

This module provides a component for visualizing filled vertical regions
between x-coordinates.
"""

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


from collections.abc import Sequence
from typing import Self, Unpack

import plotly.graph_objects as go

from pysatl_cpd.analysis.visualization.abstracts.ivisual_component import IVisualComponent
from pysatl_cpd.analysis.visualization.specs import FillSpec
from pysatl_cpd.analysis.visualization.typedefs import DrawBackend, GoAxes, GoFigure, PltAxes, PltFigure
from pysatl_cpd.analysis.visualization.utils import (
    fill_spec_to_mpl_kwargs,
    fill_spec_to_plotly_trace_kwargs,
    get_matplotlib_legend_label,
    get_plotly_legend_kwargs,
    get_subplot_y_limits,
)


[docs] class VerticalFillComponent(IVisualComponent): """Component for visualising filled vertical regions between x-coordinates. Parameters ---------- backend Plotting backend. """
[docs] def __init__(self, backend: DrawBackend) -> None: super().__init__(backend) self._regions: list[tuple[float, float]] = [] self._style: FillSpec = { "fill_color": "gray", "fill_alpha": 0.3, "legend": True, }
[docs] def set_regions(self, regions: Sequence[tuple[float, float]]) -> Self: """Set vertical regions to fill. Parameters ---------- regions Sequence of (start, end) x-coordinate pairs. Returns ------- Self """ self._regions = list(regions) return self
[docs] def set_style(self, **style: Unpack[FillSpec]) -> Self: """Set style for vertical fills. Parameters ---------- **style :fill_color: Color for the filled region. :fill_alpha: Opacity between 0 and 1. Returns ------- Self """ self._style.update(style) return self
def _draw_matplotlib(self, figure: PltFigure, axes: PltAxes, add_legend: bool = False) -> None: """ Draw vertical fills on 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 these fills. """ if not self._regions: return plot_opts = fill_spec_to_mpl_kwargs(self._style) for i, region in enumerate(self._regions): axes.axvspan( *region, **plot_opts, label=get_matplotlib_legend_label(self._style, allow_legend=add_legend) if i == 0 else None, ) def _draw_plotly(self, figure: GoFigure, axes: GoAxes, add_legend: bool = False) -> None: """ Draw vertical fills on Plotly subplot. Parameters ---------- figure The Plotly figure containing the subplot. axes The subplot position (row, column) to draw on. add_legend Whether to add legend entry for these fills. """ if not self._regions: return plot_opts = fill_spec_to_plotly_trace_kwargs(self._style) # Build coordinates for all rectangles in one trace x_coords = [] y_coords = [] y_min, y_max = get_subplot_y_limits(figure, axes) # Build hover points hover_x = [] hover_y = [] hover_t = [] spacing = 11 y_hover_poses = [y_min + float(x) / spacing * (y_max - y_min) for x in range(spacing)] for start, end in self._regions: # Add rectangle coordinates with None separators x_coords.extend([start, start, end, end, start, None]) y_coords.extend([y_min, y_max, y_max, y_min, y_min, None]) # Add multiple hover points within the rectangle area x_mid = (start + end) / 2 hover_x.extend([x_mid] * spacing) hover_y.extend(y_hover_poses) hover_label = self._style.get("label", "Vertical Fill") hover_t.extend([f"[{start:.0f}, {end:.0f}] {hover_label}"] * spacing) # Add rectangle trace (no hover) figure.add_trace( go.Scatter( x=x_coords, y=y_coords, mode="lines", line={"width": 0}, hoverinfo="skip", # Rectangle doesn't show hover **plot_opts, **get_plotly_legend_kwargs(self._style, allow_legend=add_legend), ), row=axes[0], col=axes[1], ) # Add invisible points for hover detection figure.add_trace( go.Scatter( x=hover_x, y=hover_y, mode="markers", marker={ "color": self._style["fill_color"], "size": 8, "opacity": 0, # Completely invisible }, hoverinfo="text", hovertext=hover_t, hovertemplate="%{hovertext}<extra></extra>", **get_plotly_legend_kwargs(self._style, allow_legend=add_legend, primary_trace=False), ), row=axes[0], col=axes[1], )
[docs] def clear(self) -> Self: """ Clear all vertical regions. Returns ------- Self Returns self to allow method chaining. """ self._regions.clear() return self