feat(sparkline): Add show baseline option

Adds an option to the sparkline widget to render the minimum value as
a baseline symbol rather than as an empty cell.
This commit is contained in:
Caleb Bassi 2020-01-14 08:18:18 -08:00 committed by Josh McKinney
parent 9da02078fe
commit 4f5594beff
No known key found for this signature in database
GPG key ID: 722287396A903BC5

View file

@ -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<u64>,
/// 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!["▁▁▁█▇▆▅▄▃▂▁▁"]));
}
}