""" Sparkline Widget - TipTop-style mini charts for real-time metrics """ from textual.widget import Widget from textual.reactive import reactive from typing import List, Optional from rich.text import Text from rich.console import RenderableType from rich.panel import Panel class SparklineWidget(Widget): """ ASCII sparkline chart widget inspired by TipTop Shows trend visualization using Unicode block characters: ▁▂▃▄▅▆▇█ """ DEFAULT_CSS = """ SparklineWidget { height: 4; padding: 0 1; } """ data = reactive([], always_update=True) def __init__( self, title: str, data: List[float] = None, height: int = 4, color: str = "cyan", **kwargs ): super().__init__(**kwargs) self.title = title self.data = data or [] self.height = height self.color = color self.spark_chars = " ▁▂▃▄▅▆▇█" def update_data(self, new_data: List[float]) -> None: """Update sparkline data""" self.data = new_data def render(self) -> RenderableType: """Render the sparkline chart""" if not self.data: return Panel( f"{self.title}: No data", height=self.height, border_style="dim" ) # Calculate sparkline sparkline = self._create_sparkline() # Get current value and trend current = self.data[-1] if self.data else 0 trend = self._calculate_trend() # Format current value if self.title == "Flow Rate": current_str = f"{current:.0f} flows" elif self.title == "Packet Rate": current_str = f"{current:.1f} pps" else: current_str = f"{current:.1f}" # Create content lines = [ f"{self.title}: {current_str} {trend}", "", sparkline ] return Panel( "\n".join(lines), height=self.height, border_style=self.color ) def _create_sparkline(self) -> str: """Create sparkline visualization""" if len(self.data) < 2: return "─" * 40 # Normalize data data_min = min(self.data) data_max = max(self.data) data_range = data_max - data_min if data_range == 0: # All values are the same return "─" * min(len(self.data), 40) # Create sparkline sparkline_chars = [] for value in self.data[-40:]: # Last 40 values # Normalize to 0-8 range (9 spark characters) normalized = (value - data_min) / data_range char_index = int(normalized * 8) sparkline_chars.append(self.spark_chars[char_index]) return "".join(sparkline_chars) def _calculate_trend(self) -> str: """Calculate trend indicator""" if len(self.data) < 2: return "" # Compare last value to average of previous 5 current = self.data[-1] prev_avg = sum(self.data[-6:-1]) / min(5, len(self.data) - 1) if current > prev_avg * 1.1: return "↑" elif current < prev_avg * 0.9: return "↓" else: return "→"