zui_widgets/widgets/
barchart.rs

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