ratatui/widgets/
barchart.rs

1use crate::{
2    buffer::Buffer,
3    layout::{Direction, Rect},
4    style::{Style, Styled},
5    symbols::{self},
6    text::Line,
7    widgets::{block::BlockExt, Block, Widget, WidgetRef},
8};
9
10mod bar;
11mod bar_group;
12
13pub use bar::Bar;
14pub use bar_group::BarGroup;
15
16/// A chart showing values as [bars](Bar).
17///
18/// Here is a possible `BarChart` output.
19/// ```plain
20/// ┌─────────────────────────────────┐
21/// │                             ████│
22/// │                        ▅▅▅▅ ████│
23/// │            ▇▇▇▇        ████ ████│
24/// │     ▄▄▄▄   ████ ████   ████ ████│
25/// │▆10▆ █20█   █50█ █40█   █60█ █90█│
26/// │ B1   B2     B1   B2     B1   B2 │
27/// │ Group1      Group2      Group3  │
28/// └─────────────────────────────────┘
29/// ```
30///
31/// A `BarChart` is composed of a set of [`Bar`] which can be set via [`BarChart::data`].
32/// Bars can be styled globally ([`BarChart::bar_style`]) or individually ([`Bar::style`]).
33/// There are other methods available to style even more precisely. See [`Bar`] to find out about
34/// each bar component.
35///
36/// The `BarChart` widget can also show groups of bars via [`BarGroup`].
37/// A [`BarGroup`] is a set of [`Bar`], multiple can be added to a `BarChart` using
38/// [`BarChart::data`] multiple time as demonstrated in the example below.
39///
40/// The chart can have a [`Direction`] (by default the bars are [`Vertical`](Direction::Vertical)).
41/// This is set using [`BarChart::direction`].
42///
43/// Note: this is the only widget that doesn't implement `Widget` for `&T` because the current
44/// implementation modifies the internal state of self. This will be fixed in the future.
45///
46/// # Examples
47///
48/// The following example creates a `BarChart` with two groups of bars.
49/// The first group is added by an array slice (`&[(&str, u64)]`).
50/// The second group is added by a [`BarGroup`] instance.
51/// ```
52/// use ratatui::{
53///     style::{Style, Stylize},
54///     widgets::{Bar, BarChart, BarGroup, Block},
55/// };
56///
57/// BarChart::default()
58///     .block(Block::bordered().title("BarChart"))
59///     .bar_width(3)
60///     .bar_gap(1)
61///     .group_gap(3)
62///     .bar_style(Style::new().yellow().on_red())
63///     .value_style(Style::new().red().bold())
64///     .label_style(Style::new().white())
65///     .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
66///     .data(BarGroup::default().bars(&[Bar::default().value(10), Bar::default().value(20)]))
67///     .max(4);
68/// ```
69#[derive(Debug, Clone, Eq, PartialEq, Hash)]
70pub struct BarChart<'a> {
71    /// Block to wrap the widget in
72    block: Option<Block<'a>>,
73    /// The width of each bar
74    bar_width: u16,
75    /// The gap between each bar
76    bar_gap: u16,
77    /// The gap between each group
78    group_gap: u16,
79    /// Set of symbols used to display the data
80    bar_set: symbols::bar::Set,
81    /// Style of the bars
82    bar_style: Style,
83    /// Style of the values printed at the bottom of each bar
84    value_style: Style,
85    /// Style of the labels printed under each bar
86    label_style: Style,
87    /// Style for the widget
88    style: Style,
89    /// vector of groups containing bars
90    data: Vec<BarGroup<'a>>,
91    /// Value necessary for a bar to reach the maximum height (if no value is specified,
92    /// the maximum value in the data is taken as reference)
93    max: Option<u64>,
94    /// direction of the bars
95    direction: Direction,
96}
97
98impl<'a> Default for BarChart<'a> {
99    fn default() -> Self {
100        Self {
101            block: None,
102            max: None,
103            data: Vec::new(),
104            bar_style: Style::default(),
105            bar_width: 1,
106            bar_gap: 1,
107            value_style: Style::default(),
108            label_style: Style::default(),
109            group_gap: 0,
110            bar_set: symbols::bar::NINE_LEVELS,
111            style: Style::default(),
112            direction: Direction::Vertical,
113        }
114    }
115}
116
117impl<'a> BarChart<'a> {
118    /// Add group of bars to the `BarChart`
119    ///
120    /// # Examples
121    ///
122    /// The following example creates a `BarChart` with two groups of bars.
123    /// The first group is added by an array slice (`&[(&str, u64)]`).
124    /// The second group is added by a [`BarGroup`] instance.
125    /// ```
126    /// use ratatui::widgets::{Bar, BarChart, BarGroup};
127    ///
128    /// BarChart::default()
129    ///     .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
130    ///     .data(BarGroup::default().bars(&[Bar::default().value(10), Bar::default().value(20)]));
131    /// ```
132    #[must_use = "method moves the value of self and returns the modified value"]
133    pub fn data(mut self, data: impl Into<BarGroup<'a>>) -> Self {
134        let group: BarGroup = data.into();
135        if !group.bars.is_empty() {
136            self.data.push(group);
137        }
138        self
139    }
140
141    /// Surround the [`BarChart`] with a [`Block`].
142    #[must_use = "method moves the value of self and returns the modified value"]
143    pub fn block(mut self, block: Block<'a>) -> Self {
144        self.block = Some(block);
145        self
146    }
147
148    /// Set the value necessary for a [`Bar`] to reach the maximum height.
149    ///
150    /// If not set, the maximum value in the data is taken as reference.
151    ///
152    /// # Examples
153    ///
154    /// This example shows the default behavior when `max` is not set.
155    /// The maximum value in the dataset is taken (here, `100`).
156    /// ```
157    /// use ratatui::widgets::BarChart;
158    /// BarChart::default().data(&[("foo", 1), ("bar", 2), ("baz", 100)]);
159    /// // Renders
160    /// //     █
161    /// //     █
162    /// // f b b
163    /// ```
164    ///
165    /// This example shows a custom max value.
166    /// The maximum height being `2`, `bar` & `baz` render as the max.
167    /// ```
168    /// use ratatui::widgets::BarChart;
169    ///
170    /// BarChart::default()
171    ///     .data(&[("foo", 1), ("bar", 2), ("baz", 100)])
172    ///     .max(2);
173    /// // Renders
174    /// //   █ █
175    /// // █ █ █
176    /// // f b b
177    /// ```
178    #[must_use = "method moves the value of self and returns the modified value"]
179    pub const fn max(mut self, max: u64) -> Self {
180        self.max = Some(max);
181        self
182    }
183
184    /// Set the default style of the bar.
185    ///
186    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
187    /// your own type that implements [`Into<Style>`]).
188    ///
189    /// It is also possible to set individually the style of each [`Bar`].
190    /// In this case the default style will be patched by the individual style
191    ///
192    /// [`Color`]: crate::style::Color
193    #[must_use = "method moves the value of self and returns the modified value"]
194    pub fn bar_style<S: Into<Style>>(mut self, style: S) -> Self {
195        self.bar_style = style.into();
196        self
197    }
198
199    /// Set the width of the displayed bars.
200    ///
201    /// For [`Horizontal`](crate::layout::Direction::Horizontal) bars this becomes the height of
202    /// the bar.
203    ///
204    /// If not set, this defaults to `1`.
205    /// The bar label also uses this value as its width.
206    #[must_use = "method moves the value of self and returns the modified value"]
207    pub const fn bar_width(mut self, width: u16) -> Self {
208        self.bar_width = width;
209        self
210    }
211
212    /// Set the gap between each bar.
213    ///
214    /// If not set, this defaults to `1`.
215    /// The bar label will never be larger than the bar itself, even if the gap is sufficient.
216    ///
217    /// # Example
218    ///
219    /// This shows two bars with a gap of `3`. Notice the labels will always stay under the bar.
220    /// ```
221    /// use ratatui::widgets::BarChart;
222    ///
223    /// BarChart::default()
224    ///     .data(&[("foo", 1), ("bar", 2)])
225    ///     .bar_gap(3);
226    /// // Renders
227    /// //     █
228    /// // █   █
229    /// // f   b
230    /// ```
231    #[must_use = "method moves the value of self and returns the modified value"]
232    pub const fn bar_gap(mut self, gap: u16) -> Self {
233        self.bar_gap = gap;
234        self
235    }
236
237    /// The [`bar::Set`](crate::symbols::bar::Set) to use for displaying the bars.
238    ///
239    /// If not set, the default is [`bar::NINE_LEVELS`](crate::symbols::bar::NINE_LEVELS).
240    #[must_use = "method moves the value of self and returns the modified value"]
241    pub const fn bar_set(mut self, bar_set: symbols::bar::Set) -> Self {
242        self.bar_set = bar_set;
243        self
244    }
245
246    /// Set the default value style of the bar.
247    ///
248    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
249    /// your own type that implements [`Into<Style>`]).
250    ///
251    /// It is also possible to set individually the value style of each [`Bar`].
252    /// In this case the default value style will be patched by the individual value style
253    ///
254    /// # See also
255    ///
256    /// [`Bar::value_style`] to set the value style individually.
257    ///
258    /// [`Color`]: crate::style::Color
259    #[must_use = "method moves the value of self and returns the modified value"]
260    pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
261        self.value_style = style.into();
262        self
263    }
264
265    /// Set the default label style of the groups and bars.
266    ///
267    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
268    /// your own type that implements [`Into<Style>`]).
269    ///
270    /// It is also possible to set individually the label style of each [`Bar`] or [`BarGroup`].
271    /// In this case the default label style will be patched by the individual label style
272    ///
273    /// # See also
274    ///
275    /// [`Bar::label`] to set the label style individually.
276    ///
277    /// [`Color`]: crate::style::Color
278    #[must_use = "method moves the value of self and returns the modified value"]
279    pub fn label_style<S: Into<Style>>(mut self, style: S) -> Self {
280        self.label_style = style.into();
281        self
282    }
283
284    /// Set the gap between [`BarGroup`].
285    #[must_use = "method moves the value of self and returns the modified value"]
286    pub const fn group_gap(mut self, gap: u16) -> Self {
287        self.group_gap = gap;
288        self
289    }
290
291    /// Set the style of the entire chart.
292    ///
293    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
294    /// your own type that implements [`Into<Style>`]).
295    ///
296    /// The style will be applied to everything that isn't styled (borders, bars, labels, ...).
297    ///
298    /// [`Color`]: crate::style::Color
299    #[must_use = "method moves the value of self and returns the modified value"]
300    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
301        self.style = style.into();
302        self
303    }
304
305    /// Set the direction of the bars.
306    ///
307    /// [`Vertical`](crate::layout::Direction::Vertical) bars are the default.
308    ///
309    /// # Examples
310    ///
311    /// Vertical bars
312    /// ```plain
313    ///   █
314    /// █ █
315    /// f b
316    /// ```
317    ///
318    /// Horizontal bars
319    /// ```plain
320    /// █foo██
321    ///
322    /// █bar██
323    /// ```
324    #[must_use = "method moves the value of self and returns the modified value"]
325    pub const fn direction(mut self, direction: Direction) -> Self {
326        self.direction = direction;
327        self
328    }
329}
330
331#[derive(Clone, Copy)]
332struct LabelInfo {
333    group_label_visible: bool,
334    bar_label_visible: bool,
335    height: u16,
336}
337
338impl BarChart<'_> {
339    /// Returns the visible bars length in ticks. A cell contains 8 ticks.
340    /// `available_space` used to calculate how many bars can fit in the space
341    /// `bar_max_length` is the maximal length a bar can take.
342    fn group_ticks(&self, available_space: u16, bar_max_length: u16) -> Vec<Vec<u64>> {
343        let max: u64 = self.maximum_data_value();
344        self.data
345            .iter()
346            .scan(available_space, |space, group| {
347                if *space == 0 {
348                    return None;
349                }
350                let n_bars = group.bars.len() as u16;
351                let group_width = n_bars * self.bar_width + n_bars.saturating_sub(1) * self.bar_gap;
352
353                let n_bars = if *space > group_width {
354                    *space = space.saturating_sub(group_width + self.group_gap + self.bar_gap);
355                    Some(n_bars)
356                } else {
357                    let max_bars = (*space + self.bar_gap) / (self.bar_width + self.bar_gap);
358                    if max_bars > 0 {
359                        *space = 0;
360                        Some(max_bars)
361                    } else {
362                        None
363                    }
364                };
365
366                n_bars.map(|n| {
367                    group
368                        .bars
369                        .iter()
370                        .take(n as usize)
371                        .map(|bar| bar.value * u64::from(bar_max_length) * 8 / max)
372                        .collect()
373                })
374            })
375            .collect()
376    }
377
378    /// Get label information.
379    ///
380    /// height is the number of lines, which depends on whether we need to print the bar
381    /// labels and/or the group labels.
382    /// - If there are no labels, height is 0.
383    /// - If there are only bar labels, height is 1.
384    /// - If there are only group labels, height is 1.
385    /// - If there are both bar and group labels, height is 2.
386    fn label_info(&self, available_height: u16) -> LabelInfo {
387        if available_height == 0 {
388            return LabelInfo {
389                group_label_visible: false,
390                bar_label_visible: false,
391                height: 0,
392            };
393        }
394
395        let bar_label_visible = self
396            .data
397            .iter()
398            .any(|e| e.bars.iter().any(|e| e.label.is_some()));
399
400        if available_height == 1 && bar_label_visible {
401            return LabelInfo {
402                group_label_visible: false,
403                bar_label_visible: true,
404                height: 1,
405            };
406        }
407
408        let group_label_visible = self.data.iter().any(|e| e.label.is_some());
409        LabelInfo {
410            group_label_visible,
411            bar_label_visible,
412            // convert true to 1 and false to 0 and add the two values
413            height: u16::from(group_label_visible) + u16::from(bar_label_visible),
414        }
415    }
416
417    fn render_horizontal(&self, buf: &mut Buffer, area: Rect) {
418        // get the longest label
419        let label_size = self
420            .data
421            .iter()
422            .flat_map(|group| group.bars.iter().map(|bar| &bar.label))
423            .flatten() // bar.label is an Option<Line>
424            .map(Line::width)
425            .max()
426            .unwrap_or(0) as u16;
427
428        let label_x = area.x;
429        let bars_area = {
430            let margin = u16::from(label_size != 0);
431            Rect {
432                x: area.x + label_size + margin,
433                width: area.width - label_size - margin,
434                ..area
435            }
436        };
437
438        let group_ticks = self.group_ticks(bars_area.height, bars_area.width);
439
440        // print all visible bars, label and values
441        let mut bar_y = bars_area.top();
442        for (ticks_vec, group) in group_ticks.into_iter().zip(self.data.iter()) {
443            for (ticks, bar) in ticks_vec.into_iter().zip(group.bars.iter()) {
444                let bar_length = (ticks / 8) as u16;
445                let bar_style = self.bar_style.patch(bar.style);
446
447                for y in 0..self.bar_width {
448                    let bar_y = bar_y + y;
449                    for x in 0..bars_area.width {
450                        let symbol = if x < bar_length {
451                            self.bar_set.full
452                        } else {
453                            self.bar_set.empty
454                        };
455                        buf[(bars_area.left() + x, bar_y)]
456                            .set_symbol(symbol)
457                            .set_style(bar_style);
458                    }
459                }
460
461                let bar_value_area = Rect {
462                    y: bar_y + (self.bar_width >> 1),
463                    ..bars_area
464                };
465
466                // label
467                if let Some(label) = &bar.label {
468                    buf.set_line(label_x, bar_value_area.top(), label, label_size);
469                }
470
471                bar.render_value_with_different_styles(
472                    buf,
473                    bar_value_area,
474                    bar_length as usize,
475                    self.value_style,
476                    self.bar_style,
477                );
478
479                bar_y += self.bar_gap + self.bar_width;
480            }
481
482            // if group_gap is zero, then there is no place to print the group label
483            // check also if the group label is still inside the visible area
484            let label_y = bar_y - self.bar_gap;
485            if self.group_gap > 0 && label_y < bars_area.bottom() {
486                let label_rect = Rect {
487                    y: label_y,
488                    ..bars_area
489                };
490                group.render_label(buf, label_rect, self.label_style);
491                bar_y += self.group_gap;
492            }
493        }
494    }
495
496    fn render_vertical(&self, buf: &mut Buffer, area: Rect) {
497        let label_info = self.label_info(area.height - 1);
498
499        let bars_area = Rect {
500            height: area.height - label_info.height,
501            ..area
502        };
503
504        let group_ticks = self.group_ticks(bars_area.width, bars_area.height);
505        self.render_vertical_bars(bars_area, buf, &group_ticks);
506        self.render_labels_and_values(area, buf, label_info, &group_ticks);
507    }
508
509    fn render_vertical_bars(&self, area: Rect, buf: &mut Buffer, group_ticks: &[Vec<u64>]) {
510        // print all visible bars (without labels and values)
511        let mut bar_x = area.left();
512        for (ticks_vec, group) in group_ticks.iter().zip(&self.data) {
513            for (ticks, bar) in ticks_vec.iter().zip(&group.bars) {
514                let mut ticks = *ticks;
515                for j in (0..area.height).rev() {
516                    let symbol = match ticks {
517                        0 => self.bar_set.empty,
518                        1 => self.bar_set.one_eighth,
519                        2 => self.bar_set.one_quarter,
520                        3 => self.bar_set.three_eighths,
521                        4 => self.bar_set.half,
522                        5 => self.bar_set.five_eighths,
523                        6 => self.bar_set.three_quarters,
524                        7 => self.bar_set.seven_eighths,
525                        _ => self.bar_set.full,
526                    };
527
528                    let bar_style = self.bar_style.patch(bar.style);
529
530                    for x in 0..self.bar_width {
531                        buf[(bar_x + x, area.top() + j)]
532                            .set_symbol(symbol)
533                            .set_style(bar_style);
534                    }
535
536                    ticks = ticks.saturating_sub(8);
537                }
538                bar_x += self.bar_gap + self.bar_width;
539            }
540            bar_x += self.group_gap;
541        }
542    }
543
544    /// get the maximum data value. the returned value is always greater equal 1
545    fn maximum_data_value(&self) -> u64 {
546        self.max
547            .unwrap_or_else(|| {
548                self.data
549                    .iter()
550                    .map(|group| group.max().unwrap_or_default())
551                    .max()
552                    .unwrap_or_default()
553            })
554            .max(1)
555    }
556
557    fn render_labels_and_values(
558        &self,
559        area: Rect,
560        buf: &mut Buffer,
561        label_info: LabelInfo,
562        group_ticks: &[Vec<u64>],
563    ) {
564        // print labels and values in one go
565        let mut bar_x = area.left();
566        let bar_y = area.bottom() - label_info.height - 1;
567        for (group, ticks_vec) in self.data.iter().zip(group_ticks) {
568            if group.bars.is_empty() {
569                continue;
570            }
571            // print group labels under the bars or the previous labels
572            if label_info.group_label_visible {
573                let label_max_width =
574                    ticks_vec.len() as u16 * (self.bar_width + self.bar_gap) - self.bar_gap;
575                let group_area = Rect {
576                    x: bar_x,
577                    y: area.bottom() - 1,
578                    width: label_max_width,
579                    height: 1,
580                };
581                group.render_label(buf, group_area, self.label_style);
582            }
583
584            // print the bar values and numbers
585            for (bar, ticks) in group.bars.iter().zip(ticks_vec) {
586                if label_info.bar_label_visible {
587                    bar.render_label(buf, self.bar_width, bar_x, bar_y + 1, self.label_style);
588                }
589
590                bar.render_value(buf, self.bar_width, bar_x, bar_y, self.value_style, *ticks);
591
592                bar_x += self.bar_gap + self.bar_width;
593            }
594            bar_x += self.group_gap;
595        }
596    }
597}
598
599impl Widget for BarChart<'_> {
600    fn render(self, area: Rect, buf: &mut Buffer) {
601        self.render_ref(area, buf);
602    }
603}
604
605impl WidgetRef for BarChart<'_> {
606    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
607        buf.set_style(area, self.style);
608
609        self.block.render_ref(area, buf);
610        let inner = self.block.inner_if_some(area);
611
612        if inner.is_empty() || self.data.is_empty() || self.bar_width == 0 {
613            return;
614        }
615
616        match self.direction {
617            Direction::Horizontal => self.render_horizontal(buf, inner),
618            Direction::Vertical => self.render_vertical(buf, inner),
619        }
620    }
621}
622
623impl<'a> Styled for BarChart<'a> {
624    type Item = Self;
625    fn style(&self) -> Style {
626        self.style
627    }
628
629    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
630        self.style(style)
631    }
632}
633
634#[cfg(test)]
635mod tests {
636    use itertools::iproduct;
637
638    use super::*;
639    use crate::{
640        layout::Alignment,
641        style::{Color, Modifier, Stylize},
642        text::Span,
643        widgets::BorderType,
644    };
645
646    #[test]
647    fn default() {
648        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
649        let widget = BarChart::default();
650        widget.render(buffer.area, &mut buffer);
651        assert_eq!(buffer, Buffer::with_lines(["          "; 3]));
652    }
653
654    #[test]
655    fn data() {
656        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
657        let widget = BarChart::default().data(&[("foo", 1), ("bar", 2)]);
658        widget.render(buffer.area, &mut buffer);
659        #[rustfmt::skip]
660        let expected = Buffer::with_lines([
661            "  █       ",
662            "1 2       ",
663            "f b       ",
664        ]);
665        assert_eq!(buffer, expected);
666    }
667
668    #[test]
669    fn block() {
670        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));
671        let block = Block::bordered()
672            .border_type(BorderType::Double)
673            .title("Block");
674        let widget = BarChart::default()
675            .data(&[("foo", 1), ("bar", 2)])
676            .block(block);
677        widget.render(buffer.area, &mut buffer);
678        let expected = Buffer::with_lines([
679            "╔Block═══╗",
680            "║  █     ║",
681            "║1 2     ║",
682            "║f b     ║",
683            "╚════════╝",
684        ]);
685        assert_eq!(buffer, expected);
686    }
687
688    #[test]
689    fn max() {
690        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
691        let without_max = BarChart::default().data(&[("foo", 1), ("bar", 2), ("baz", 100)]);
692        without_max.render(buffer.area, &mut buffer);
693        #[rustfmt::skip]
694        let expected = Buffer::with_lines([
695            "    █     ",
696            "    █     ",
697            "f b b     ",
698        ]);
699        assert_eq!(buffer, expected);
700        let with_max = BarChart::default()
701            .data(&[("foo", 1), ("bar", 2), ("baz", 100)])
702            .max(2);
703        with_max.render(buffer.area, &mut buffer);
704        #[rustfmt::skip]
705        let expected = Buffer::with_lines([
706            "  █ █     ",
707            "1 2 █     ",
708            "f b b     ",
709        ]);
710        assert_eq!(buffer, expected);
711    }
712
713    #[test]
714    fn bar_style() {
715        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
716        let widget = BarChart::default()
717            .data(&[("foo", 1), ("bar", 2)])
718            .bar_style(Style::new().red());
719        widget.render(buffer.area, &mut buffer);
720        #[rustfmt::skip]
721        let mut expected = Buffer::with_lines([
722            "  █       ",
723            "1 2       ",
724            "f b       ",
725        ]);
726        for (x, y) in iproduct!([0, 2], [0, 1]) {
727            expected[(x, y)].set_fg(Color::Red);
728        }
729        assert_eq!(buffer, expected);
730    }
731
732    #[test]
733    fn bar_width() {
734        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
735        let widget = BarChart::default()
736            .data(&[("foo", 1), ("bar", 2)])
737            .bar_width(3);
738        widget.render(buffer.area, &mut buffer);
739        #[rustfmt::skip]
740        let expected = Buffer::with_lines([
741            "    ███   ",
742            "█1█ █2█   ",
743            "foo bar   ",
744        ]);
745        assert_eq!(buffer, expected);
746    }
747
748    #[test]
749    fn bar_gap() {
750        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
751        let widget = BarChart::default()
752            .data(&[("foo", 1), ("bar", 2)])
753            .bar_gap(2);
754        widget.render(buffer.area, &mut buffer);
755        #[rustfmt::skip]
756        let expected = Buffer::with_lines([
757            "   █      ",
758            "1  2      ",
759            "f  b      ",
760        ]);
761        assert_eq!(buffer, expected);
762    }
763
764    #[test]
765    fn bar_set() {
766        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
767        let widget = BarChart::default()
768            .data(&[("foo", 0), ("bar", 1), ("baz", 3)])
769            .bar_set(symbols::bar::THREE_LEVELS);
770        widget.render(buffer.area, &mut buffer);
771        #[rustfmt::skip]
772        let expected = Buffer::with_lines([
773            "    █     ",
774            "  ▄ 3     ",
775            "f b b     ",
776        ]);
777        assert_eq!(buffer, expected);
778    }
779
780    #[test]
781    fn bar_set_nine_levels() {
782        let mut buffer = Buffer::empty(Rect::new(0, 0, 18, 3));
783        let widget = BarChart::default()
784            .data(&[
785                ("a", 0),
786                ("b", 1),
787                ("c", 2),
788                ("d", 3),
789                ("e", 4),
790                ("f", 5),
791                ("g", 6),
792                ("h", 7),
793                ("i", 8),
794            ])
795            .bar_set(symbols::bar::NINE_LEVELS);
796        widget.render(Rect::new(0, 1, 18, 2), &mut buffer);
797        let expected = Buffer::with_lines([
798            "                  ",
799            "  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8 ",
800            "a b c d e f g h i ",
801        ]);
802        assert_eq!(buffer, expected);
803    }
804
805    #[test]
806    fn value_style() {
807        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
808        let widget = BarChart::default()
809            .data(&[("foo", 1), ("bar", 2)])
810            .bar_width(3)
811            .value_style(Style::new().red());
812        widget.render(buffer.area, &mut buffer);
813        #[rustfmt::skip]
814        let mut expected = Buffer::with_lines([
815            "    ███   ",
816            "█1█ █2█   ",
817            "foo bar   ",
818        ]);
819        expected[(1, 1)].set_fg(Color::Red);
820        expected[(5, 1)].set_fg(Color::Red);
821        assert_eq!(buffer, expected);
822    }
823
824    #[test]
825    fn label_style() {
826        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
827        let widget = BarChart::default()
828            .data(&[("foo", 1), ("bar", 2)])
829            .label_style(Style::new().red());
830        widget.render(buffer.area, &mut buffer);
831        #[rustfmt::skip]
832        let mut expected = Buffer::with_lines([
833            "  █       ",
834            "1 2       ",
835            "f b       ",
836        ]);
837        expected[(0, 2)].set_fg(Color::Red);
838        expected[(2, 2)].set_fg(Color::Red);
839        assert_eq!(buffer, expected);
840    }
841
842    #[test]
843    fn style() {
844        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
845        let widget = BarChart::default()
846            .data(&[("foo", 1), ("bar", 2)])
847            .style(Style::new().red());
848        widget.render(buffer.area, &mut buffer);
849        #[rustfmt::skip]
850        let mut expected = Buffer::with_lines([
851            "  █       ",
852            "1 2       ",
853            "f b       ",
854        ]);
855        for (x, y) in iproduct!(0..10, 0..3) {
856            expected[(x, y)].set_fg(Color::Red);
857        }
858        assert_eq!(buffer, expected);
859    }
860
861    #[test]
862    fn can_be_stylized() {
863        assert_eq!(
864            BarChart::default().black().on_white().bold().style,
865            Style::default()
866                .fg(Color::Black)
867                .bg(Color::White)
868                .add_modifier(Modifier::BOLD)
869        );
870    }
871
872    #[test]
873    fn test_empty_group() {
874        let chart = BarChart::default()
875            .data(BarGroup::default().label("invisible".into()))
876            .data(
877                BarGroup::default()
878                    .label("G".into())
879                    .bars(&[Bar::default().value(1), Bar::default().value(2)]),
880            );
881
882        let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
883        chart.render(buffer.area, &mut buffer);
884        #[rustfmt::skip]
885        let expected = Buffer::with_lines([
886            "  █",
887            "1 2",
888            "G  ",
889        ]);
890        assert_eq!(buffer, expected);
891    }
892
893    fn build_test_barchart<'a>() -> BarChart<'a> {
894        BarChart::default()
895            .data(BarGroup::default().label("G1".into()).bars(&[
896                Bar::default().value(2),
897                Bar::default().value(3),
898                Bar::default().value(4),
899            ]))
900            .data(BarGroup::default().label("G2".into()).bars(&[
901                Bar::default().value(3),
902                Bar::default().value(4),
903                Bar::default().value(5),
904            ]))
905            .group_gap(1)
906            .direction(Direction::Horizontal)
907            .bar_gap(0)
908    }
909
910    #[test]
911    fn test_horizontal_bars() {
912        let chart: BarChart<'_> = build_test_barchart();
913
914        let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 8));
915        chart.render(buffer.area, &mut buffer);
916        let expected = Buffer::with_lines([
917            "2█   ",
918            "3██  ",
919            "4███ ",
920            "G1   ",
921            "3██  ",
922            "4███ ",
923            "5████",
924            "G2   ",
925        ]);
926        assert_eq!(buffer, expected);
927    }
928
929    #[test]
930    fn test_horizontal_bars_no_space_for_group_label() {
931        let chart: BarChart<'_> = build_test_barchart();
932
933        let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 7));
934        chart.render(buffer.area, &mut buffer);
935        let expected = Buffer::with_lines([
936            "2█   ",
937            "3██  ",
938            "4███ ",
939            "G1   ",
940            "3██  ",
941            "4███ ",
942            "5████",
943        ]);
944        assert_eq!(buffer, expected);
945    }
946
947    #[test]
948    fn test_horizontal_bars_no_space_for_all_bars() {
949        let chart: BarChart<'_> = build_test_barchart();
950
951        let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 5));
952        chart.render(buffer.area, &mut buffer);
953        #[rustfmt::skip]
954        let expected = Buffer::with_lines([
955            "2█   ",
956            "3██  ",
957            "4███ ",
958            "G1   ",
959            "3██  ",
960        ]);
961        assert_eq!(buffer, expected);
962    }
963
964    fn test_horizontal_bars_label_width_greater_than_bar(bar_color: Option<Color>) {
965        let mut bar = Bar::default()
966            .value(2)
967            .text_value("label".into())
968            .value_style(Style::default().red());
969
970        if let Some(color) = bar_color {
971            bar = bar.style(Style::default().fg(color));
972        }
973
974        let chart: BarChart<'_> = BarChart::default()
975            .data(BarGroup::default().bars(&[bar, Bar::default().value(5)]))
976            .direction(Direction::Horizontal)
977            .bar_style(Style::default().yellow())
978            .value_style(Style::default().italic())
979            .bar_gap(0);
980
981        let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
982        chart.render(buffer.area, &mut buffer);
983
984        let mut expected = Buffer::with_lines(["label", "5████"]);
985
986        // first line has a yellow foreground. first cell contains italic "5"
987        expected[(0, 1)].modifier.insert(Modifier::ITALIC);
988        for x in 0..5 {
989            expected[(x, 1)].set_fg(Color::Yellow);
990        }
991
992        let expected_color = bar_color.unwrap_or(Color::Yellow);
993
994        // second line contains the word "label". Since the bar value is 2,
995        // then the first 2 characters of "label" are italic red.
996        // the rest is white (using the Bar's style).
997        let cell = expected[(0, 0)].set_fg(Color::Red);
998        cell.modifier.insert(Modifier::ITALIC);
999        let cell = expected[(1, 0)].set_fg(Color::Red);
1000        cell.modifier.insert(Modifier::ITALIC);
1001        expected[(2, 0)].set_fg(expected_color);
1002        expected[(3, 0)].set_fg(expected_color);
1003        expected[(4, 0)].set_fg(expected_color);
1004
1005        assert_eq!(buffer, expected);
1006    }
1007
1008    #[test]
1009    fn test_horizontal_bars_label_width_greater_than_bar_without_style() {
1010        test_horizontal_bars_label_width_greater_than_bar(None);
1011    }
1012
1013    #[test]
1014    fn test_horizontal_bars_label_width_greater_than_bar_with_style() {
1015        test_horizontal_bars_label_width_greater_than_bar(Some(Color::White));
1016    }
1017
1018    /// Tests horizontal bars label are presents
1019    #[test]
1020    fn test_horizontal_label() {
1021        let chart = BarChart::default()
1022            .direction(Direction::Horizontal)
1023            .bar_gap(0)
1024            .data(&[("Jan", 10), ("Feb", 20), ("Mar", 5)]);
1025
1026        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1027        chart.render(buffer.area, &mut buffer);
1028        #[rustfmt::skip]
1029        let expected = Buffer::with_lines([
1030            "Jan 10█   ",
1031            "Feb 20████",
1032            "Mar 5     ",
1033        ]);
1034        assert_eq!(buffer, expected);
1035    }
1036
1037    #[test]
1038    fn test_group_label_style() {
1039        let chart: BarChart<'_> = BarChart::default()
1040            .data(
1041                BarGroup::default()
1042                    .label(Span::from("G1").red().into())
1043                    .bars(&[Bar::default().value(2)]),
1044            )
1045            .group_gap(1)
1046            .direction(Direction::Horizontal)
1047            .label_style(Style::default().bold().yellow());
1048
1049        let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
1050        chart.render(buffer.area, &mut buffer);
1051
1052        // G1 should have the bold red style
1053        // bold: because of BarChart::label_style
1054        // red: is included with the label itself
1055        let mut expected = Buffer::with_lines(["2████", "G1   "]);
1056        let cell = expected[(0, 1)].set_fg(Color::Red);
1057        cell.modifier.insert(Modifier::BOLD);
1058        let cell = expected[(1, 1)].set_fg(Color::Red);
1059        cell.modifier.insert(Modifier::BOLD);
1060
1061        assert_eq!(buffer, expected);
1062    }
1063
1064    #[test]
1065    fn test_group_label_center() {
1066        // test the centered group position when one bar is outside the group
1067        let group = BarGroup::from(&[("a", 1), ("b", 2), ("c", 3), ("c", 4)]);
1068        let chart = BarChart::default()
1069            .data(
1070                group
1071                    .clone()
1072                    .label(Line::from("G1").alignment(Alignment::Center)),
1073            )
1074            .data(group.label(Line::from("G2").alignment(Alignment::Center)));
1075
1076        let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
1077        chart.render(buffer.area, &mut buffer);
1078        let expected = Buffer::with_lines([
1079            "    ▂ █     ▂",
1080            "  ▄ █ █   ▄ █",
1081            "▆ 2 3 4 ▆ 2 3",
1082            "a b c c a b c",
1083            "  G1     G2  ",
1084        ]);
1085        assert_eq!(buffer, expected);
1086    }
1087
1088    #[test]
1089    fn test_group_label_right() {
1090        let chart: BarChart<'_> = BarChart::default().data(
1091            BarGroup::default()
1092                .label(Line::from(Span::from("G")).alignment(Alignment::Right))
1093                .bars(&[Bar::default().value(2), Bar::default().value(5)]),
1094        );
1095
1096        let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
1097        chart.render(buffer.area, &mut buffer);
1098        #[rustfmt::skip]
1099        let expected = Buffer::with_lines([
1100            "  █",
1101            "▆ 5",
1102            "  G",
1103        ]);
1104        assert_eq!(buffer, expected);
1105    }
1106
1107    #[test]
1108    fn test_unicode_as_value() {
1109        let group = BarGroup::default().bars(&[
1110            Bar::default()
1111                .value(123)
1112                .label("B1".into())
1113                .text_value("写".into()),
1114            Bar::default()
1115                .value(321)
1116                .label("B2".into())
1117                .text_value("写".into()),
1118            Bar::default()
1119                .value(333)
1120                .label("B2".into())
1121                .text_value("写".into()),
1122        ]);
1123        let chart = BarChart::default().data(group).bar_width(3).bar_gap(1);
1124
1125        let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 5));
1126        chart.render(buffer.area, &mut buffer);
1127        let expected = Buffer::with_lines([
1128            "    ▆▆▆ ███",
1129            "    ███ ███",
1130            "▃▃▃ ███ ███",
1131            "写█ 写█ 写█",
1132            "B1  B2  B2 ",
1133        ]);
1134        assert_eq!(buffer, expected);
1135    }
1136
1137    #[test]
1138    fn handles_zero_width() {
1139        // this test is to ensure that a BarChart with zero bar / gap width does not panic
1140        let chart = BarChart::default()
1141            .data(&[("A", 1)])
1142            .bar_width(0)
1143            .bar_gap(0);
1144        let mut buffer = Buffer::empty(Rect::new(0, 0, 0, 10));
1145        chart.render(buffer.area, &mut buffer);
1146        assert_eq!(buffer, Buffer::empty(Rect::new(0, 0, 0, 10)));
1147    }
1148
1149    #[test]
1150    fn single_line() {
1151        let mut group: BarGroup = (&[
1152            ("a", 0),
1153            ("b", 1),
1154            ("c", 2),
1155            ("d", 3),
1156            ("e", 4),
1157            ("f", 5),
1158            ("g", 6),
1159            ("h", 7),
1160            ("i", 8),
1161        ])
1162            .into();
1163        group = group.label("Group".into());
1164
1165        let chart = BarChart::default()
1166            .data(group)
1167            .bar_set(symbols::bar::NINE_LEVELS);
1168
1169        let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 1));
1170        chart.render(buffer.area, &mut buffer);
1171        assert_eq!(buffer, Buffer::with_lines(["  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8"]));
1172    }
1173
1174    #[test]
1175    fn two_lines() {
1176        let mut group: BarGroup = (&[
1177            ("a", 0),
1178            ("b", 1),
1179            ("c", 2),
1180            ("d", 3),
1181            ("e", 4),
1182            ("f", 5),
1183            ("g", 6),
1184            ("h", 7),
1185            ("i", 8),
1186        ])
1187            .into();
1188        group = group.label("Group".into());
1189
1190        let chart = BarChart::default()
1191            .data(group)
1192            .bar_set(symbols::bar::NINE_LEVELS);
1193
1194        let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
1195        chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
1196        let expected = Buffer::with_lines([
1197            "                 ",
1198            "  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
1199            "a b c d e f g h i",
1200        ]);
1201        assert_eq!(buffer, expected);
1202    }
1203
1204    #[test]
1205    fn three_lines() {
1206        let mut group: BarGroup = (&[
1207            ("a", 0),
1208            ("b", 1),
1209            ("c", 2),
1210            ("d", 3),
1211            ("e", 4),
1212            ("f", 5),
1213            ("g", 6),
1214            ("h", 7),
1215            ("i", 8),
1216        ])
1217            .into();
1218        group = group.label(Line::from("Group").alignment(Alignment::Center));
1219
1220        let chart = BarChart::default()
1221            .data(group)
1222            .bar_set(symbols::bar::NINE_LEVELS);
1223
1224        let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
1225        chart.render(buffer.area, &mut buffer);
1226        let expected = Buffer::with_lines([
1227            "  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
1228            "a b c d e f g h i",
1229            "      Group      ",
1230        ]);
1231        assert_eq!(buffer, expected);
1232    }
1233
1234    #[test]
1235    fn three_lines_double_width() {
1236        let mut group = BarGroup::from(&[
1237            ("a", 0),
1238            ("b", 1),
1239            ("c", 2),
1240            ("d", 3),
1241            ("e", 4),
1242            ("f", 5),
1243            ("g", 6),
1244            ("h", 7),
1245            ("i", 8),
1246        ]);
1247        group = group.label(Line::from("Group").alignment(Alignment::Center));
1248
1249        let chart = BarChart::default()
1250            .data(group)
1251            .bar_width(2)
1252            .bar_set(symbols::bar::NINE_LEVELS);
1253
1254        let mut buffer = Buffer::empty(Rect::new(0, 0, 26, 3));
1255        chart.render(buffer.area, &mut buffer);
1256        let expected = Buffer::with_lines([
1257            "   1▁ 2▂ 3▃ 4▄ 5▅ 6▆ 7▇ 8█",
1258            "a  b  c  d  e  f  g  h  i ",
1259            "          Group           ",
1260        ]);
1261        assert_eq!(buffer, expected);
1262    }
1263
1264    #[test]
1265    fn four_lines() {
1266        let mut group: BarGroup = (&[
1267            ("a", 0),
1268            ("b", 1),
1269            ("c", 2),
1270            ("d", 3),
1271            ("e", 4),
1272            ("f", 5),
1273            ("g", 6),
1274            ("h", 7),
1275            ("i", 8),
1276        ])
1277            .into();
1278        group = group.label(Line::from("Group").alignment(Alignment::Center));
1279
1280        let chart = BarChart::default()
1281            .data(group)
1282            .bar_set(symbols::bar::NINE_LEVELS);
1283
1284        let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 4));
1285        chart.render(buffer.area, &mut buffer);
1286        let expected = Buffer::with_lines([
1287            "          ▂ ▄ ▆ █",
1288            "  ▂ ▄ ▆ 4 5 6 7 8",
1289            "a b c d e f g h i",
1290            "      Group      ",
1291        ]);
1292        assert_eq!(buffer, expected);
1293    }
1294
1295    #[test]
1296    fn two_lines_without_bar_labels() {
1297        let group = BarGroup::default()
1298            .label(Line::from("Group").alignment(Alignment::Center))
1299            .bars(&[
1300                Bar::default().value(0),
1301                Bar::default().value(1),
1302                Bar::default().value(2),
1303                Bar::default().value(3),
1304                Bar::default().value(4),
1305                Bar::default().value(5),
1306                Bar::default().value(6),
1307                Bar::default().value(7),
1308                Bar::default().value(8),
1309            ]);
1310
1311        let chart = BarChart::default().data(group);
1312
1313        let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
1314        chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
1315        let expected = Buffer::with_lines([
1316            "                 ",
1317            "  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
1318            "      Group      ",
1319        ]);
1320        assert_eq!(buffer, expected);
1321    }
1322
1323    #[test]
1324    fn one_lines_with_more_bars() {
1325        let bars: Vec<Bar> = (0..30).map(|i| Bar::default().value(i)).collect();
1326
1327        let chart = BarChart::default().data(BarGroup::default().bars(&bars));
1328
1329        let mut buffer = Buffer::empty(Rect::new(0, 0, 59, 1));
1330        chart.render(buffer.area, &mut buffer);
1331        let expected =
1332            Buffer::with_lines(["        ▁ ▁ ▁ ▁ ▂ ▂ ▂ ▃ ▃ ▃ ▃ ▄ ▄ ▄ ▄ ▅ ▅ ▅ ▆ ▆ ▆ ▆ ▇ ▇ ▇ █"]);
1333        assert_eq!(buffer, expected);
1334    }
1335
1336    #[test]
1337    fn first_bar_of_the_group_is_half_outside_view() {
1338        let chart = BarChart::default()
1339            .data(&[("a", 1), ("b", 2)])
1340            .data(&[("a", 1), ("b", 2)])
1341            .bar_width(2);
1342
1343        let mut buffer = Buffer::empty(Rect::new(0, 0, 7, 6));
1344        chart.render(buffer.area, &mut buffer);
1345        let expected = Buffer::with_lines([
1346            "   ██  ",
1347            "   ██  ",
1348            "▄▄ ██  ",
1349            "██ ██  ",
1350            "1█ 2█  ",
1351            "a  b   ",
1352        ]);
1353        assert_eq!(buffer, expected);
1354    }
1355}