Skip to main content

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