ratatui_widgets/
barchart.rs

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