altui_core/widgets/
sparkline.rs1use crate::{
2 buffer::Buffer,
3 layout::Rect,
4 style::Style,
5 symbols,
6 widgets::{Block, Widget},
7};
8use std::cmp::min;
9
10#[derive(Debug, Clone)]
24pub struct Sparkline<'a> {
25 block: Option<Block<'a>>,
27 style: Style,
29 data: &'a [u64],
31 max: Option<u64>,
34 bar_set: symbols::bar::Set,
36}
37
38impl<'a> Default for Sparkline<'a> {
39 fn default() -> Sparkline<'a> {
40 Sparkline {
41 block: None,
42 style: Default::default(),
43 data: &[],
44 max: None,
45 bar_set: symbols::bar::NINE_LEVELS,
46 }
47 }
48}
49
50impl<'a> Sparkline<'a> {
51 pub fn block(mut self, block: Block<'a>) -> Sparkline<'a> {
52 self.block = Some(block);
53 self
54 }
55
56 pub fn style(mut self, style: Style) -> Sparkline<'a> {
57 self.style = style;
58 self
59 }
60
61 pub fn data(mut self, data: &'a [u64]) -> Sparkline<'a> {
62 self.data = data;
63 self
64 }
65
66 pub fn max(mut self, max: u64) -> Sparkline<'a> {
67 self.max = Some(max);
68 self
69 }
70
71 pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> Sparkline<'a> {
72 self.bar_set = bar_set;
73 self
74 }
75}
76
77impl<'a> Widget for Sparkline<'a> {
78 fn render(&mut self, area: Rect, buf: &mut Buffer) {
79 let spark_area = match self.block.as_mut() {
80 Some(b) => {
81 let inner_area = b.inner(area);
82 b.render(area, buf);
83 inner_area
84 }
85 None => area,
86 };
87
88 if spark_area.height < 1 {
89 return;
90 }
91
92 let max = match self.max {
93 Some(v) => v,
94 None => *self.data.iter().max().unwrap_or(&1u64),
95 };
96 let max_index = min(spark_area.width as usize, self.data.len());
97 let mut data = self
98 .data
99 .iter()
100 .take(max_index)
101 .map(|e| {
102 if max != 0 {
103 e * u64::from(spark_area.height) * 8 / max
104 } else {
105 0
106 }
107 })
108 .collect::<Vec<u64>>();
109 for j in (0..spark_area.height).rev() {
110 for (i, d) in data.iter_mut().enumerate() {
111 let symbol = match *d {
112 0 => self.bar_set.empty,
113 1 => self.bar_set.one_eighth,
114 2 => self.bar_set.one_quarter,
115 3 => self.bar_set.three_eighths,
116 4 => self.bar_set.half,
117 5 => self.bar_set.five_eighths,
118 6 => self.bar_set.three_quarters,
119 7 => self.bar_set.seven_eighths,
120 _ => self.bar_set.full,
121 };
122 buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j)
123 .set_symbol(symbol)
124 .set_style(self.style);
125
126 if *d > 8 {
127 *d -= 8;
128 } else {
129 *d = 0;
130 }
131 }
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn it_does_not_panic_if_max_is_zero() {
142 let mut widget = Sparkline::default().data(&[0, 0, 0]);
143 let area = Rect::new(0, 0, 3, 1);
144 let mut buffer = Buffer::empty(area);
145 widget.render(area, &mut buffer);
146 }
147
148 #[test]
149 fn it_does_not_panic_if_max_is_set_to_zero() {
150 let mut widget = Sparkline::default().data(&[0, 1, 2]).max(0);
151 let area = Rect::new(0, 0, 3, 1);
152 let mut buffer = Buffer::empty(area);
153 widget.render(area, &mut buffer);
154 }
155}