from __future__ import annotations from typing import Callable, ClassVar, Optional, Sequence from textual.app import RenderResult from textual.color import Color from textual.reactive import reactive from textual.renderables.sparkline import Sparkline as SparklineRenderable from textual.widget import Widget def _max_factory() -> Callable[[Sequence[float]], float]: """Callable that returns the built-in max to initialise a reactive.""" return max class Sparkline(Widget): """A sparkline widget to display numerical data.""" COMPONENT_CLASSES: ClassVar[set[str]] = { "sparkline--max-color", "sparkline--min-color", } """ Use these component classes to define the two colors that the sparkline interpolates to represent its numerical data. Note: These two component classes are used exclusively for the _color_ of the sparkline widget. Setting any style other than [`color`](/styles/color.md) will have no effect. | Class | Description | | :- | :- | | `sparkline--max-color` | The color used for the larger values in the data. | | `sparkline--min-color` | The color used for the smaller values in the data. | """ DEFAULT_CSS = """ Sparkline { height: 1; } Sparkline > .sparkline--max-color { color: $primary; } Sparkline > .sparkline--min-color { color: $primary 30%; } """ data = reactive[Optional[Sequence[float]]](None) """The data that populates the sparkline.""" summary_function = reactive[Callable[[Sequence[float]], float]](_max_factory) """The function that computes the value that represents each bar.""" def __init__( self, data: Sequence[float] | None = None, *, min_color: Color | str | None = None, max_color: Color | str | None = None, summary_function: Callable[[Sequence[float]], float] | None = None, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, ) -> None: """Initialize a sparkline widget. Args: data: The initial data to populate the sparkline with. min_color: The color of the minimum value, or `None` to take from CSS. max_color: the color of the maximum value, or `None` to take from CSS. summary_function: Summarizes bar values into a single value used to represent each bar. name: The name of the widget. id: The ID of the widget in the DOM. classes: The CSS classes for the widget. disabled: Whether the widget is disabled or not. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) self.min_color = None if min_color is None else Color.parse(min_color) self.max_color = None if max_color is None else Color.parse(max_color) self.data = data if summary_function is not None: self.summary_function = summary_function def render(self) -> RenderResult: """Renders the sparkline when there is data available.""" data = self.data or [] _, base = self.background_colors min_color = base + ( self.get_component_styles("sparkline--min-color").color if self.min_color is None else self.min_color ) max_color = base + ( self.get_component_styles("sparkline--max-color").color if self.max_color is None else self.max_color ) return SparklineRenderable( data, width=self.size.width, min_color=min_color.rich_color, max_color=max_color.rich_color, summary_function=self.summary_function, )