use crate::{ buffer::Buffer, layout::Rect, style::Style, symbols, widgets::{Block, Widget}, }; use std::cmp::{max, min}; use unicode_width::UnicodeWidthStr; /// Display multiple bars in a single widgets /// /// # Examples /// /// ``` /// # use tui::widgets::{Block, Borders, BarChart}; /// # use tui::style::{Style, Color, Modifier}; /// BarChart::default() /// .block(Block::default().title("BarChart").borders(Borders::ALL)) /// .bar_width(3) /// .bar_gap(1) /// .style(Style::default().fg(Color::Yellow).bg(Color::Red)) /// .value_style(Style::default().fg(Color::Red).modifier(Modifier::BOLD)) /// .label_style(Style::default().fg(Color::White)) /// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)]) /// .max(4); /// ``` #[derive(Debug, Clone)] pub struct BarChart<'a> { /// Block to wrap the widget in block: Option>, /// The width of each bar bar_width: u16, /// The gap between each bar bar_gap: u16, /// Set of symbols used to display the data bar_set: symbols::bar::Set, /// Style of the values printed at the bottom of each bar value_style: Style, /// Style of the labels printed under each bar label_style: Style, /// Style for the widget style: Style, /// Slice of (label, value) pair to plot on the chart data: &'a [(&'a str, u64)], /// Value necessary for a bar to reach the maximum height (if no value is specified, /// the maximum value in the data is taken as reference) max: Option, /// Values to display on the bar (computed when the data is passed to the widget) values: Vec, } impl<'a> Default for BarChart<'a> { fn default() -> BarChart<'a> { BarChart { block: None, max: None, data: &[], values: Vec::new(), bar_width: 1, bar_gap: 1, bar_set: symbols::bar::NINE_LEVELS, value_style: Default::default(), label_style: Default::default(), style: Default::default(), } } } impl<'a> BarChart<'a> { pub fn data(mut self, data: &'a [(&'a str, u64)]) -> BarChart<'a> { self.data = data; self.values = Vec::with_capacity(self.data.len()); for &(_, v) in self.data { self.values.push(format!("{}", v)); } self } pub fn block(mut self, block: Block<'a>) -> BarChart<'a> { self.block = Some(block); self } pub fn max(mut self, max: u64) -> BarChart<'a> { self.max = Some(max); self } pub fn bar_width(mut self, width: u16) -> BarChart<'a> { self.bar_width = width; self } pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> { self.bar_gap = gap; self } pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> BarChart<'a> { self.bar_set = bar_set; self } pub fn value_style(mut self, style: Style) -> BarChart<'a> { self.value_style = style; self } pub fn label_style(mut self, style: Style) -> BarChart<'a> { self.label_style = style; self } pub fn style(mut self, style: Style) -> BarChart<'a> { self.style = style; self } } impl<'a> Widget for BarChart<'a> { fn render(mut self, area: Rect, buf: &mut Buffer) { let chart_area = match self.block.take() { Some(b) => { let inner_area = b.inner(area); b.render(area, buf); inner_area } None => area, }; if chart_area.height < 2 { return; } buf.set_background(chart_area, self.style.bg); let max = self .max .unwrap_or_else(|| 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 * u64::from(chart_area.height) * 8 / std::cmp::max(max, 1), ) }) .collect::>(); for j in (0..chart_area.height - 1).rev() { for (i, d) in data.iter_mut().enumerate() { let symbol = match d.1 { 0 => self.bar_set.empty, 1 => self.bar_set.one_eighth, 2 => self.bar_set.one_quarter, 3 => self.bar_set.three_eighths, 4 => self.bar_set.half, 5 => self.bar_set.five_eighths, 6 => self.bar_set.three_quarters, 7 => self.bar_set.seven_eighths, _ => self.bar_set.full, }; for x in 0..self.bar_width { buf.get_mut( chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x, chart_area.top() + j, ) .set_symbol(symbol) .set_style(self.style); } 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( chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + (self.bar_width - width) / 2, chart_area.bottom() - 2, value_label, self.value_style, ); } } buf.set_stringn( chart_area.left() + i as u16 * (self.bar_width + self.bar_gap), chart_area.bottom() - 1, label, self.bar_width as usize, self.label_style, ); } } }