diff --git a/examples/prototype.rs b/examples/prototype.rs index 6a4e682..cb48008 100644 --- a/examples/prototype.rs +++ b/examples/prototype.rs @@ -21,7 +21,8 @@ use log4rs::encode::pattern::PatternEncoder; use log4rs::config::{Appender, Config, Root}; use tui::Terminal; -use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset}; +use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset, + BarChart}; use tui::layout::{Group, Direction, Alignment, Size}; use tui::style::Color; @@ -82,6 +83,7 @@ struct App<'a> { data: Vec, data2: Vec<(f64, f64)>, data3: Vec<(f64, f64)>, + data4: Vec<(&'a str, u64)>, window: [f64; 2], colors: [Color; 2], color_index: usize, @@ -124,6 +126,18 @@ fn main() { data: rand_signal.clone().take(200).collect(), data2: sin_signal.clone().take(20).collect(), data3: sin_signal2.clone().take(20).collect(), + data4: vec![("B1", 9), + ("B2", 12), + ("B3", 5), + ("B4", 8), + ("B5", 2), + ("B6", 4), + ("B7", 5), + ("B8", 9), + ("B9", 14), + ("B10", 15), + ("B11", 1), + ("B12", 0)], window: [0.0, 20.0], colors: [Color::Magenta, Color::Red], color_index: 0, @@ -151,7 +165,7 @@ fn main() { let tx = tx.clone(); loop { tx.send(Event::Tick).unwrap(); - thread::sleep(time::Duration::from_millis(200)); + thread::sleep(time::Duration::from_millis(500)); } }); @@ -196,6 +210,8 @@ fn main() { app.data2.push(sin_signal.next().unwrap()); app.data3.remove(0); app.data3.push(sin_signal2.next().unwrap()); + let i = app.data4.pop().unwrap(); + app.data4.insert(0, i); app.window[0] += 1.0; app.window[1] += 1.0; app.selected += 1; @@ -270,7 +286,16 @@ fn draw(t: &mut Terminal, app: &App) { .block(Block::default().borders(border::ALL).title("List")) .items(&app.items2) .render(&chunks[1], t); - }) + }); + BarChart::default() + .block(Block::default().borders(border::ALL).title("Bar chart")) + .data(&app.data4) + .bar_width(3) + .bar_gap(2) + .bar_color(Color::LightGreen) + .value_color(Color::Black) + .label_color(Color::LightYellow) + .render(&chunks[1], t); }); if app.show_chart { Chart::default() diff --git a/src/buffer.rs b/src/buffer.rs index 3e18b0f..27324e4 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,3 +1,6 @@ +use std::cmp::min; +use std::usize; + use unicode_segmentation::UnicodeSegmentation; use layout::Rect; @@ -115,13 +118,23 @@ impl<'a> Buffer<'a> { } pub fn set_string(&mut self, x: u16, y: u16, string: &'a str, fg: Color, bg: Color) { + self.set_characters(x, y, string, usize::MAX, fg, bg); + } + + pub fn set_characters(&mut self, + x: u16, + y: u16, + string: &'a str, + limit: usize, + fg: Color, + bg: Color) { let index = self.index_of(x, y); if index.is_none() { return; } let mut index = index.unwrap(); let graphemes = UnicodeSegmentation::graphemes(string, true).collect::>(); - let max_index = (self.area.width - x) as usize; + let max_index = min((self.area.width - x) as usize, limit); for s in graphemes.iter().take(max_index) { self.content[index].symbol = s; self.content[index].fg = fg; @@ -130,6 +143,7 @@ impl<'a> Buffer<'a> { } } + pub fn update_colors(&mut self, x: u16, y: u16, fg: Color, bg: Color) { if let Some(i) = self.index_of(x, y) { self.content[i].fg = fg; diff --git a/src/widgets/barchart.rs b/src/widgets/barchart.rs new file mode 100644 index 0000000..9f71416 --- /dev/null +++ b/src/widgets/barchart.rs @@ -0,0 +1,155 @@ +use std::cmp::{min, max}; + +use unicode_width::UnicodeWidthStr; + +use widgets::{Widget, Block}; +use buffer::Buffer; +use layout::Rect; +use style::Color; +use symbols::bar; + +pub struct BarChart<'a> { + block: Option>, + max: Option, + bar_width: u16, + bar_gap: u16, + bar_color: Color, + value_color: Color, + label_color: Color, + data: &'a [(&'a str, u64)], + values: Vec, +} + +impl<'a> Default for BarChart<'a> { + fn default() -> BarChart<'a> { + BarChart { + block: None, + max: None, + bar_width: 1, + bar_gap: 1, + bar_color: Color::Reset, + value_color: Color::Reset, + label_color: Color::Reset, + data: &[], + values: Vec::new(), + } + } +} + +impl<'a> BarChart<'a> { + pub fn data(&'a mut self, data: &'a [(&'a str, u64)]) -> &mut BarChart<'a> { + self.data = data; + self.values = Vec::with_capacity(self.data.len()); + for &(l, v) in self.data { + self.values.push(format!("{}", v)); + } + self + } + + pub fn block(&'a mut self, block: Block<'a>) -> &mut BarChart<'a> { + self.block = Some(block); + self + } + pub fn max(&'a mut self, max: u64) -> &mut BarChart<'a> { + self.max = Some(max); + self + } + + pub fn bar_width(&'a mut self, width: u16) -> &mut BarChart<'a> { + self.bar_width = width; + self + } + pub fn bar_gap(&'a mut self, gap: u16) -> &mut BarChart<'a> { + self.bar_gap = gap; + self + } + pub fn bar_color(&'a mut self, color: Color) -> &mut BarChart<'a> { + self.bar_color = color; + self + } + pub fn value_color(&'a mut self, color: Color) -> &mut BarChart<'a> { + self.value_color = color; + self + } + pub fn label_color(&'a mut self, color: Color) -> &mut BarChart<'a> { + self.label_color = color; + self + } +} + +impl<'a> Widget<'a> for BarChart<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a> { + let (mut buf, chart_area) = match self.block { + Some(ref b) => (b.buffer(area), b.inner(*area)), + None => (Buffer::empty(*area), *area), + }; + + if chart_area.height < 1 { + return buf; + } + + let margin_x = chart_area.x - area.x; + let margin_y = chart_area.y - area.y; + let max = self.max.unwrap_or(self.data.iter().fold(0, |acc, &(_, v)| max(v, acc))); + let max_index = min((chart_area.width / (self.bar_width + self.bar_gap)) as usize, + self.data.len()); + let mut data = self.data + .iter() + .take(max_index) + .map(|&(l, v)| (l, v * chart_area.height as u64 * 8 / max)) + .collect::>(); + for j in (0..chart_area.height - 1).rev() { + for (i, d) in data.iter_mut().enumerate() { + let symbol = match d.1 { + 0 => " ", + 1 => bar::ONE_EIGHTH, + 2 => bar::ONE_QUATER, + 3 => bar::THREE_EIGHTHS, + 4 => bar::HALF, + 5 => bar::FIVE_EIGHTHS, + 6 => bar::THREE_QUATERS, + 7 => bar::SEVEN_EIGHTHS, + _ => bar::FULL, + }; + + for x in 0..self.bar_width { + buf.update_cell(margin_x + i as u16 * (self.bar_width + self.bar_gap) + x, + margin_y + j, + symbol, + self.bar_color, + Color::Reset); + } + + if d.1 > 8 { + d.1 -= 8; + } else { + d.1 = 0; + } + + } + } + + for (i, &(label, value)) in self.data.iter().take(max_index).enumerate() { + if value != 0 { + let value_label = &self.values[i]; + let width = value_label.width() as u16; + if width < self.bar_width { + buf.set_string(margin_x + i as u16 * (self.bar_width + self.bar_gap) + + (self.bar_width - width) / 2, + chart_area.height - 1, + value_label, + self.value_color, + self.bar_color); + } + } + buf.set_characters(margin_x + i as u16 * (self.bar_width + self.bar_gap), + chart_area.height, + label, + self.bar_width as usize, + self.label_color, + Color::Reset); + } + + buf + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index acd90c6..976ef26 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -4,6 +4,7 @@ mod list; mod gauge; mod sparkline; mod chart; +mod barchart; pub use self::block::Block; pub use self::text::Text; @@ -11,6 +12,7 @@ pub use self::list::List; pub use self::gauge::Gauge; pub use self::sparkline::Sparkline; pub use self::chart::{Chart, Axis, Dataset}; +pub use self::barchart::BarChart; use buffer::Buffer; use layout::Rect; diff --git a/src/widgets/sparkline.rs b/src/widgets/sparkline.rs index 9bfb1f2..f4d0caf 100644 --- a/src/widgets/sparkline.rs +++ b/src/widgets/sparkline.rs @@ -72,10 +72,11 @@ impl<'a> Widget<'a> for Sparkline<'a> { let max_index = min(spark_area.width as usize, self.data.len()); let mut data = self.data .iter() + .take(max_index) .map(|e| e * spark_area.height as u64 * 8 / max) .collect::>(); for j in (0..spark_area.height).rev() { - for (i, d) in data.iter_mut().take(max_index).enumerate() { + for (i, d) in data.iter_mut().enumerate() { let symbol = match *d { 0 => " ", 1 => bar::ONE_EIGHTH,