use retroglyph_core::{Rect, Style};
use super::{Meter, Widget};
use crate::Surface;
const BLOCKS: [char; 9] = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
#[derive(Clone, Copy, Debug)]
pub struct Sparkline<'a> {
samples: &'a [f32],
style: Option<Style>,
}
impl<'a> Sparkline<'a> {
#[must_use]
pub const fn new(samples: &'a [f32]) -> Self {
Self {
samples,
style: None,
}
}
#[must_use]
pub const fn style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
}
impl Widget for Sparkline<'_> {
fn render(&self, area: Rect, surface: &mut Surface<'_>) {
let width = area.width_usize();
if width == 0 {
return;
}
let max = self
.samples
.iter()
.copied()
.fold(0.0_f32, f32::max)
.max(1e-6);
let start = self.samples.len().saturating_sub(width);
let recent = &self.samples[start..];
let pad = width - recent.len();
let y = area.top();
for i in 0..width {
#[allow(clippy::cast_possible_truncation)]
let x = area.left() + i as u16;
if i < pad {
surface.put((x, y), ' ', Style::new());
continue;
}
let ratio = (recent[i - pad] / max).clamp(0.0, 1.0);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let level = (ratio * 8.0).round() as usize;
let style = self
.style
.unwrap_or_else(|| Style::new().fg(Meter::new(ratio).color()));
surface.put((x, y), BLOCKS[level.min(8)], style);
}
}
}
#[cfg(test)]
mod tests {
use retroglyph_core::{Grid, Pos};
use super::*;
#[test]
fn right_aligns_recent_samples_and_pads_the_rest() {
let area = Rect::new(0, 0, 5, 1);
let mut grid = Grid::new(5, 1);
Sparkline::new(&[1.0, 2.0]).render(area, &mut Surface::new(&mut grid, area, 0));
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(2, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(3, 0)].glyph(), BLOCKS[4]); assert_eq!(grid[Pos::new(4, 0)].glyph(), BLOCKS[8]); }
#[test]
fn empty_samples_is_a_no_op_beyond_blank_padding() {
let area = Rect::new(0, 0, 3, 1);
let mut grid = Grid::new(3, 1);
Sparkline::new(&[]).render(area, &mut Surface::new(&mut grid, area, 0));
for x in 0..3 {
assert_eq!(grid[Pos::new(x, 0)].glyph(), ' ');
}
}
#[test]
fn style_overrides_the_default_meter_ramp_with_one_fixed_color() {
use retroglyph_core::Color;
let area = Rect::new(0, 0, 3, 1);
let mut grid = Grid::new(3, 1);
let accent = Style::new().fg(Color::Rgb {
r: 90,
g: 170,
b: 250,
});
Sparkline::new(&[1.0, 4.0, 2.0])
.style(accent)
.render(area, &mut Surface::new(&mut grid, area, 0));
assert_ne!(grid[Pos::new(0, 0)].glyph(), grid[Pos::new(1, 0)].glyph());
for x in 0..3 {
assert_eq!(grid[Pos::new(x, 0)].style(), accent);
}
}
}