tui_temp_fork/widgets/
sparkline.rs

1use std::cmp::min;
2
3use crate::buffer::Buffer;
4use crate::layout::Rect;
5use crate::style::Style;
6use crate::symbols::bar;
7use crate::widgets::{Block, Widget};
8
9/// Widget to render a sparkline over one or more lines.
10///
11/// # Examples
12///
13/// ```
14/// # use tui_temp_fork::widgets::{Block, Borders, Sparkline};
15/// # use tui_temp_fork::style::{Style, Color};
16/// # fn main() {
17/// Sparkline::default()
18///     .block(Block::default().title("Sparkline").borders(Borders::ALL))
19///     .data(&[0, 2, 3, 4, 1, 4, 10])
20///     .max(5)
21///     .style(Style::default().fg(Color::Red).bg(Color::White));
22/// # }
23/// ```
24pub struct Sparkline<'a> {
25    /// A block to wrap the widget in
26    block: Option<Block<'a>>,
27    /// Widget style
28    style: Style,
29    /// A slice of the data to display
30    data: &'a [u64],
31    /// The maximum value to take to compute the maximum bar height (if nothing is specified, the
32    /// widget uses the max of the dataset)
33    max: Option<u64>,
34}
35
36impl<'a> Default for Sparkline<'a> {
37    fn default() -> Sparkline<'a> {
38        Sparkline {
39            block: None,
40            style: Default::default(),
41            data: &[],
42            max: None,
43        }
44    }
45}
46
47impl<'a> Sparkline<'a> {
48    pub fn block(mut self, block: Block<'a>) -> Sparkline<'a> {
49        self.block = Some(block);
50        self
51    }
52
53    pub fn style(mut self, style: Style) -> Sparkline<'a> {
54        self.style = style;
55        self
56    }
57
58    pub fn data(mut self, data: &'a [u64]) -> Sparkline<'a> {
59        self.data = data;
60        self
61    }
62
63    pub fn max(mut self, max: u64) -> Sparkline<'a> {
64        self.max = Some(max);
65        self
66    }
67}
68
69impl<'a> Widget for Sparkline<'a> {
70    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
71        let spark_area = match self.block {
72            Some(ref mut b) => {
73                b.draw(area, buf);
74                b.inner(area)
75            }
76            None => area,
77        };
78
79        if spark_area.height < 1 {
80            return;
81        }
82
83        let max = match self.max {
84            Some(v) => v,
85            None => *self.data.iter().max().unwrap_or(&1u64),
86        };
87        let max_index = min(spark_area.width as usize, self.data.len());
88        let mut data = self
89            .data
90            .iter()
91            .take(max_index)
92            .map(|e| {
93                if max != 0 {
94                    e * u64::from(spark_area.height) * 8 / max
95                } else {
96                    0
97                }
98            })
99            .collect::<Vec<u64>>();
100        for j in (0..spark_area.height).rev() {
101            for (i, d) in data.iter_mut().enumerate() {
102                let symbol = match *d {
103                    0 => " ",
104                    1 => bar::ONE_EIGHTH,
105                    2 => bar::ONE_QUARTER,
106                    3 => bar::THREE_EIGHTHS,
107                    4 => bar::HALF,
108                    5 => bar::FIVE_EIGHTHS,
109                    6 => bar::THREE_QUARTERS,
110                    7 => bar::SEVEN_EIGHTHS,
111                    _ => bar::FULL,
112                };
113                buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j)
114                    .set_symbol(symbol)
115                    .set_fg(self.style.fg)
116                    .set_bg(self.style.bg);
117
118                if *d > 8 {
119                    *d -= 8;
120                } else {
121                    *d = 0;
122                }
123            }
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn it_does_not_panic_if_max_is_zero() {
134        let mut widget = Sparkline::default().data(&[0, 0, 0]);
135        let area = Rect::new(0, 0, 3, 1);
136        let mut buffer = Buffer::empty(area);
137        widget.draw(area, &mut buffer);
138    }
139
140    #[test]
141    fn it_does_not_panic_if_max_is_set_to_zero() {
142        let mut widget = Sparkline::default().data(&[0, 1, 2]).max(0);
143        let area = Rect::new(0, 0, 3, 1);
144        let mut buffer = Buffer::empty(area);
145        widget.draw(area, &mut buffer);
146    }
147}