tui_temp_fork/widgets/
barchart.rs

1use std::cmp::{max, min};
2
3use unicode_width::UnicodeWidthStr;
4
5use crate::buffer::Buffer;
6use crate::layout::Rect;
7use crate::style::Style;
8use crate::symbols::bar;
9use crate::widgets::{Block, Widget};
10
11/// Display multiple bars in a single widgets
12///
13/// # Examples
14///
15/// ```
16/// # use tui_temp_fork::widgets::{Block, Borders, BarChart};
17/// # use tui_temp_fork::style::{Style, Color, Modifier};
18/// # fn main() {
19/// BarChart::default()
20///     .block(Block::default().title("BarChart").borders(Borders::ALL))
21///     .bar_width(3)
22///     .bar_gap(1)
23///     .style(Style::default().fg(Color::Yellow).bg(Color::Red))
24///     .value_style(Style::default().fg(Color::Red).modifier(Modifier::BOLD))
25///     .label_style(Style::default().fg(Color::White))
26///     .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
27///     .max(4);
28/// # }
29/// ```
30pub struct BarChart<'a> {
31    /// Block to wrap the widget in
32    block: Option<Block<'a>>,
33    /// The width of each bar
34    bar_width: u16,
35    /// The gap between each bar
36    bar_gap: u16,
37    /// Style of the values printed at the bottom of each bar
38    value_style: Style,
39    /// Style of the labels printed under each bar
40    label_style: Style,
41    /// Style for the widget
42    style: Style,
43    /// Slice of (label, value) pair to plot on the chart
44    data: &'a [(&'a str, u64)],
45    /// Value necessary for a bar to reach the maximum height (if no value is specified,
46    /// the maximum value in the data is taken as reference)
47    max: Option<u64>,
48    /// Values to display on the bar (computed when the data is passed to the widget)
49    values: Vec<String>,
50}
51
52impl<'a> Default for BarChart<'a> {
53    fn default() -> BarChart<'a> {
54        BarChart {
55            block: None,
56            max: None,
57            data: &[],
58            values: Vec::new(),
59            bar_width: 1,
60            bar_gap: 1,
61            value_style: Default::default(),
62            label_style: Default::default(),
63            style: Default::default(),
64        }
65    }
66}
67
68impl<'a> BarChart<'a> {
69    pub fn data(mut self, data: &'a [(&'a str, u64)]) -> BarChart<'a> {
70        self.data = data;
71        self.values = Vec::with_capacity(self.data.len());
72        for &(_, v) in self.data {
73            self.values.push(format!("{}", v));
74        }
75        self
76    }
77
78    pub fn block(mut self, block: Block<'a>) -> BarChart<'a> {
79        self.block = Some(block);
80        self
81    }
82    pub fn max(mut self, max: u64) -> BarChart<'a> {
83        self.max = Some(max);
84        self
85    }
86
87    pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
88        self.bar_width = width;
89        self
90    }
91    pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> {
92        self.bar_gap = gap;
93        self
94    }
95    pub fn value_style(mut self, style: Style) -> BarChart<'a> {
96        self.value_style = style;
97        self
98    }
99    pub fn label_style(mut self, style: Style) -> BarChart<'a> {
100        self.label_style = style;
101        self
102    }
103    pub fn style(mut self, style: Style) -> BarChart<'a> {
104        self.style = style;
105        self
106    }
107}
108
109impl<'a> Widget for BarChart<'a> {
110    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
111        let chart_area = match self.block {
112            Some(ref mut b) => {
113                b.draw(area, buf);
114                b.inner(area)
115            }
116            None => area,
117        };
118
119        if chart_area.height < 2 {
120            return;
121        }
122
123        self.background(chart_area, buf, self.style.bg);
124
125        let max = self
126            .max
127            .unwrap_or_else(|| self.data.iter().fold(0, |acc, &(_, v)| max(v, acc)));
128        let max_index = min(
129            (chart_area.width / (self.bar_width + self.bar_gap)) as usize,
130            self.data.len(),
131        );
132        let mut data = self
133            .data
134            .iter()
135            .take(max_index)
136            .map(|&(l, v)| {
137                (
138                    l,
139                    v * u64::from(chart_area.height) * 8 / std::cmp::max(max, 1),
140                )
141            })
142            .collect::<Vec<(&str, u64)>>();
143        for j in (0..chart_area.height - 1).rev() {
144            for (i, d) in data.iter_mut().enumerate() {
145                let symbol = match d.1 {
146                    0 => " ",
147                    1 => bar::ONE_EIGHTH,
148                    2 => bar::ONE_QUARTER,
149                    3 => bar::THREE_EIGHTHS,
150                    4 => bar::HALF,
151                    5 => bar::FIVE_EIGHTHS,
152                    6 => bar::THREE_QUARTERS,
153                    7 => bar::SEVEN_EIGHTHS,
154                    _ => bar::FULL,
155                };
156
157                for x in 0..self.bar_width {
158                    buf.get_mut(
159                        chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
160                        chart_area.top() + j,
161                    )
162                    .set_symbol(symbol)
163                    .set_style(self.style);
164                }
165
166                if d.1 > 8 {
167                    d.1 -= 8;
168                } else {
169                    d.1 = 0;
170                }
171            }
172        }
173
174        for (i, &(label, value)) in self.data.iter().take(max_index).enumerate() {
175            if value != 0 {
176                let value_label = &self.values[i];
177                let width = value_label.width() as u16;
178                if width < self.bar_width {
179                    buf.set_string(
180                        chart_area.left()
181                            + i as u16 * (self.bar_width + self.bar_gap)
182                            + (self.bar_width - width) / 2,
183                        chart_area.bottom() - 2,
184                        value_label,
185                        self.value_style,
186                    );
187                }
188            }
189            buf.set_stringn(
190                chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
191                chart_area.bottom() - 1,
192                label,
193                self.bar_width as usize,
194                self.label_style,
195            );
196        }
197    }
198}