diff --git a/src/widgets/sparkline.rs b/src/widgets/sparkline.rs index 36f74b6f..a32e9fd1 100644 --- a/src/widgets/sparkline.rs +++ b/src/widgets/sparkline.rs @@ -31,6 +31,8 @@ pub struct Sparkline<'a> { /// The maximum value to take to compute the maximum bar height (if nothing is specified, the /// widget uses the max of the dataset) max: Option, + /// If true, draws a baseline of `bar::ONE_EIGHTH` spanning the bottom of the sparkline graph + show_baseline: bool, /// A set of bar symbols used to represent the give data bar_set: symbols::bar::Set, // The direction to render the sparkine, either from left to right, or from right to left @@ -50,6 +52,7 @@ impl<'a> Default for Sparkline<'a> { style: Default::default(), data: &[], max: None, + show_baseline: false, bar_set: symbols::bar::NINE_LEVELS, direction: RenderDirection::LeftToRight, } @@ -77,6 +80,11 @@ impl<'a> Sparkline<'a> { self } + pub fn show_baseline(mut self, show_baseline: bool) -> Sparkline<'a> { + self.show_baseline = show_baseline; + self + } + pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> Sparkline<'a> { self.bar_set = bar_set; self @@ -103,6 +111,14 @@ impl<'a> Widget for Sparkline<'a> { return; } + if self.show_baseline { + for i in spark_area.left()..spark_area.right() { + buf.get_mut(i, spark_area.bottom() - 1) + .set_symbol(self.bar_set.one_eighth) + .set_style(self.style); + } + } + let max = match self.max { Some(v) => v, None => *self.data.iter().max().unwrap_or(&1u64), @@ -123,7 +139,13 @@ impl<'a> Widget for Sparkline<'a> { for j in (0..spark_area.height).rev() { for (i, d) in data.iter_mut().enumerate() { let symbol = match *d { - 0 => self.bar_set.empty, + 0 => { + if self.show_baseline && j == spark_area.height - 1 { + self.bar_set.one_eighth + } else { + self.bar_set.empty + } + } 1 => self.bar_set.one_eighth, 2 => self.bar_set.one_quarter, 3 => self.bar_set.three_eighths, @@ -158,42 +180,94 @@ mod tests { // Helper function to render a sparkline to a buffer with a given width // filled with x symbols to make it easier to assert on the result - fn render(widget: Sparkline, width: u16) -> Buffer { - let area = Rect::new(0, 0, width, 1); + fn render(widget: Sparkline, width: u16, height: u16) -> Buffer { let mut cell = Cell::default(); cell.set_symbol("x"); - let mut buffer = Buffer::filled(area, &cell); - widget.render(area, &mut buffer); + let mut buffer = Buffer::filled(Rect::new(0, 0, width, height), &cell); + widget.render(buffer.area, &mut buffer); buffer } #[test] fn it_does_not_panic_if_max_is_zero() { let widget = Sparkline::default().data(&[0, 0, 0]); - let buffer = render(widget, 6); + let buffer = render(widget, 6, 1); assert_buffer_eq!(buffer, Buffer::with_lines(vec![" xxx"])); } #[test] fn it_does_not_panic_if_max_is_set_to_zero() { let widget = Sparkline::default().data(&[0, 1, 2]).max(0); - let buffer = render(widget, 6); + let buffer = render(widget, 6, 1); assert_buffer_eq!(buffer, Buffer::with_lines(vec![" xxx"])); } #[test] - fn it_draws() { + fn it_renders() { let widget = Sparkline::default().data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]); - let buffer = render(widget, 12); + let buffer = render(widget, 12, 1); assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁▂▃▄▅▆▇█xxx"])); } + #[test] + fn it_renders_with_max_more_than_data_max() { + let widget = Sparkline::default() + .data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]) + .max(16); + let buffer = render(widget, 12, 1); + assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁▁▂▂▃▃▄xxx"])); + } + + #[test] + fn it_renders_with_max_less_than_data_max() { + let widget = Sparkline::default() + .data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]) + .max(4); + let buffer = render(widget, 12, 1); + assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▂▄▆█████xxx"])); + } + + #[test] + fn it_renders_with_multi_line() { + let widget = Sparkline::default().data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]); + let buffer = render(widget, 15, 3); + assert_buffer_eq!( + buffer, + Buffer::with_lines(vec![ + " ▂▅█xxxxxx", + " ▁▄▇███xxxxxx", + " ▃▆██████xxxxxx", + ]) + ); + } + + #[test] + fn it_renders_with_multi_line_and_baseline() { + let widget = Sparkline::default() + .data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]) + .show_baseline(true); + let buffer = render(widget, 15, 3); + assert_buffer_eq!( + buffer, + // this currently fails because the baseline logic doesn't clear + // the parts above the line + // " ▂▅█xxxxxx", + // " ▁▄▇███xxxxxx", + // " ▃▆██████▁▁▁▁▁▁", + Buffer::with_lines(vec![ + " ▂▅█ ", + " ▁▄▇███ ", + " ▃▆██████▁▁▁▁▁▁", + ]) + ); + } + #[test] fn it_renders_left_to_right() { let widget = Sparkline::default() .data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]) .direction(RenderDirection::LeftToRight); - let buffer = render(widget, 12); + let buffer = render(widget, 12, 1); assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁▂▃▄▅▆▇█xxx"])); } @@ -202,7 +276,26 @@ mod tests { let widget = Sparkline::default() .data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]) .direction(RenderDirection::RightToLeft); - let buffer = render(widget, 12); + let buffer = render(widget, 12, 1); assert_buffer_eq!(buffer, Buffer::with_lines(vec!["xxx█▇▆▅▄▃▂▁ "])); } + + #[test] + fn it_renders_baseline() { + let widget = Sparkline::default() + .data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]) + .show_baseline(true); + let buffer = render(widget, 12, 1); + assert_buffer_eq!(buffer, Buffer::with_lines(vec!["▁▁▂▃▄▅▆▇█▁▁▁"])); + } + + #[test] + fn it_renders_baseline_right_to_left() { + let widget = Sparkline::default() + .data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]) + .direction(RenderDirection::RightToLeft) + .show_baseline(true); + let buffer = render(widget, 12, 1); + assert_buffer_eq!(buffer, Buffer::with_lines(vec!["▁▁▁█▇▆▅▄▃▂▁▁"])); + } }