Module textual.renderables.sparkline
Expand source code
from __future__ import annotations
import statistics
from typing import Sequence, Iterable, Callable, TypeVar
from rich.color import Color
from rich.console import ConsoleOptions, Console, RenderResult
from rich.segment import Segment
from rich.style import Style
from textual.renderables._blend_colors import blend_colors
T = TypeVar("T", int, float)
class Sparkline:
"""A sparkline representing a series of data.
Args:
data (Sequence[T]): The sequence of data to render.
width (int, optional): The width of the sparkline/the number of buckets to partition the data into.
min_color (Color, optional): The color of values equal to the min value in data.
max_color (Color, optional): The color of values equal to the max value in data.
summary_function (Callable[list[T]]): Function that will be applied to each bucket.
"""
BARS = "▁▂▃▄▅▆▇█"
def __init__(
self,
data: Sequence[T],
*,
width: int | None,
min_color: Color = Color.from_rgb(0, 255, 0),
max_color: Color = Color.from_rgb(255, 0, 0),
summary_function: Callable[[list[T]], float] = max,
) -> None:
self.data = data
self.width = width
self.min_color = Style.from_color(min_color)
self.max_color = Style.from_color(max_color)
self.summary_function = summary_function
@classmethod
def _buckets(cls, data: Sequence[T], num_buckets: int) -> Iterable[list[T]]:
"""Partition ``data`` into ``num_buckets`` buckets. For example, the data
[1, 2, 3, 4] partitioned into 2 buckets is [[1, 2], [3, 4]].
Args:
data (Sequence[T]): The data to partition.
num_buckets (int): The number of buckets to partition the data into.
"""
num_steps, remainder = divmod(len(data), num_buckets)
for i in range(num_buckets):
start = i * num_steps + min(i, remainder)
end = (i + 1) * num_steps + min(i + 1, remainder)
partition = data[start:end]
if partition:
yield partition
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
width = self.width or options.max_width
len_data = len(self.data)
if len_data == 0:
yield Segment("▁" * width, self.min_color)
return
if len_data == 1:
yield Segment("█" * width, self.max_color)
return
minimum, maximum = min(self.data), max(self.data)
extent = maximum - minimum or 1
buckets = list(self._buckets(self.data, num_buckets=self.width))
bucket_index = 0
bars_rendered = 0
step = len(buckets) / width
summary_function = self.summary_function
min_color, max_color = self.min_color.color, self.max_color.color
while bars_rendered < width:
partition = buckets[int(bucket_index)]
partition_summary = summary_function(partition)
height_ratio = (partition_summary - minimum) / extent
bar_index = int(height_ratio * (len(self.BARS) - 1))
bar_color = blend_colors(min_color, max_color, height_ratio)
bars_rendered += 1
bucket_index += step
yield Segment(self.BARS[bar_index], Style.from_color(bar_color))
if __name__ == "__main__":
console = Console()
def last(l):
return l[-1]
funcs = min, max, last, statistics.median, statistics.mean
nums = [10, 2, 30, 60, 45, 20, 7, 8, 9, 10, 50, 13, 10, 6, 5, 4, 3, 7, 20]
console.print(f"data = {nums}\n")
for f in funcs:
console.print(
f"{f.__name__}:\t", Sparkline(nums, width=12, summary_function=f), end=""
)
console.print("\n")
Classes
class Sparkline (data: Sequence[T], *, width: int | None, min_color: Color = Color('#00ff00', ColorType.TRUECOLOR, triplet=ColorTriplet(red=0, green=255, blue=0)), max_color: Color = Color('#ff0000', ColorType.TRUECOLOR, triplet=ColorTriplet(red=255, green=0, blue=0)), summary_function: Callable[[list[T]], float] = <built-in function max>)
-
A sparkline representing a series of data.
Args
data
:Sequence[T]
- The sequence of data to render.
width
:int
, optional- The width of the sparkline/the number of buckets to partition the data into.
min_color
:Color
, optional- The color of values equal to the min value in data.
max_color
:Color
, optional- The color of values equal to the max value in data.
summary_function
:Callable[list[T]]
- Function that will be applied to each bucket.
Expand source code
class Sparkline: """A sparkline representing a series of data. Args: data (Sequence[T]): The sequence of data to render. width (int, optional): The width of the sparkline/the number of buckets to partition the data into. min_color (Color, optional): The color of values equal to the min value in data. max_color (Color, optional): The color of values equal to the max value in data. summary_function (Callable[list[T]]): Function that will be applied to each bucket. """ BARS = "▁▂▃▄▅▆▇█" def __init__( self, data: Sequence[T], *, width: int | None, min_color: Color = Color.from_rgb(0, 255, 0), max_color: Color = Color.from_rgb(255, 0, 0), summary_function: Callable[[list[T]], float] = max, ) -> None: self.data = data self.width = width self.min_color = Style.from_color(min_color) self.max_color = Style.from_color(max_color) self.summary_function = summary_function @classmethod def _buckets(cls, data: Sequence[T], num_buckets: int) -> Iterable[list[T]]: """Partition ``data`` into ``num_buckets`` buckets. For example, the data [1, 2, 3, 4] partitioned into 2 buckets is [[1, 2], [3, 4]]. Args: data (Sequence[T]): The data to partition. num_buckets (int): The number of buckets to partition the data into. """ num_steps, remainder = divmod(len(data), num_buckets) for i in range(num_buckets): start = i * num_steps + min(i, remainder) end = (i + 1) * num_steps + min(i + 1, remainder) partition = data[start:end] if partition: yield partition def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: width = self.width or options.max_width len_data = len(self.data) if len_data == 0: yield Segment("▁" * width, self.min_color) return if len_data == 1: yield Segment("█" * width, self.max_color) return minimum, maximum = min(self.data), max(self.data) extent = maximum - minimum or 1 buckets = list(self._buckets(self.data, num_buckets=self.width)) bucket_index = 0 bars_rendered = 0 step = len(buckets) / width summary_function = self.summary_function min_color, max_color = self.min_color.color, self.max_color.color while bars_rendered < width: partition = buckets[int(bucket_index)] partition_summary = summary_function(partition) height_ratio = (partition_summary - minimum) / extent bar_index = int(height_ratio * (len(self.BARS) - 1)) bar_color = blend_colors(min_color, max_color, height_ratio) bars_rendered += 1 bucket_index += step yield Segment(self.BARS[bar_index], Style.from_color(bar_color))
Class variables
var BARS