ratatui_widgets/barchart/
bar_group.rs

1use alloc::vec::Vec;
2
3use ratatui_core::buffer::Buffer;
4use ratatui_core::layout::{Alignment, Rect};
5use ratatui_core::style::Style;
6use ratatui_core::text::Line;
7use ratatui_core::widgets::Widget;
8
9use crate::barchart::Bar;
10
11/// A group of bars to be shown by the Barchart.
12///
13/// # Examples
14///
15/// ```
16/// use ratatui::widgets::{Bar, BarGroup};
17///
18/// let group = BarGroup::new([Bar::with_label("Red", 20), Bar::with_label("Blue", 15)]);
19/// ```
20#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
21pub struct BarGroup<'a> {
22    /// label of the group. It will be printed centered under this group of bars
23    pub(super) label: Option<Line<'a>>,
24    /// list of bars to be shown
25    pub(super) bars: Vec<Bar<'a>>,
26}
27
28impl<'a> BarGroup<'a> {
29    /// Creates a new `BarGroup` with the given bars.
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use ratatui::style::{Style, Stylize};
35    /// use ratatui::widgets::{Bar, BarGroup};
36    ///
37    /// let group = BarGroup::new(vec![Bar::with_label("A", 10), Bar::with_label("B", 20)]);
38    /// ```
39    pub fn new<T: Into<Vec<Bar<'a>>>>(bars: T) -> Self {
40        Self {
41            bars: bars.into(),
42            ..Self::default()
43        }
44    }
45
46    /// Creates a new `BarGroup` with the given bars and label.
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// use ratatui::style::{Style, Stylize};
52    /// use ratatui::widgets::{Bar, BarGroup};
53    ///
54    /// let group = BarGroup::with_label(
55    ///     "Group1",
56    ///     vec![Bar::with_label("A", 10), Bar::with_label("B", 20)],
57    /// );
58    /// ```
59    pub fn with_label<T: Into<Line<'a>>, B: Into<Vec<Bar<'a>>>>(label: T, bars: B) -> Self {
60        Self {
61            label: Some(label.into()),
62            bars: bars.into(),
63        }
64    }
65
66    /// Set the group label
67    ///
68    /// `label` can be a [`&str`], [`String`] or anything that can be converted into [`Line`].
69    ///
70    /// # Examples
71    ///
72    /// From [`&str`] and [`String`].
73    ///
74    /// ```rust
75    /// use ratatui::widgets::BarGroup;
76    ///
77    /// BarGroup::default().label("label");
78    /// BarGroup::default().label(String::from("label"));
79    /// ```
80    ///
81    /// From a [`Line`] with red foreground color:
82    ///
83    /// ```rust
84    /// use ratatui::style::Stylize;
85    /// use ratatui::text::Line;
86    /// use ratatui::widgets::BarGroup;
87    ///
88    /// BarGroup::default().label(Line::from("Line").red());
89    /// ```
90    /// [`String`]: alloc::string::String
91    #[must_use = "method moves the value of self and returns the modified value"]
92    pub fn label<T: Into<Line<'a>>>(mut self, label: T) -> Self {
93        self.label = Some(label.into());
94        self
95    }
96
97    /// Set the bars of the group to be shown
98    #[must_use = "method moves the value of self and returns the modified value"]
99    pub fn bars(mut self, bars: &[Bar<'a>]) -> Self {
100        self.bars = bars.to_vec();
101        self
102    }
103
104    /// The maximum bar value of this group
105    pub(super) fn max(&self) -> Option<u64> {
106        self.bars.iter().max_by_key(|v| v.value).map(|v| v.value)
107    }
108
109    pub(super) fn render_label(&self, buf: &mut Buffer, area: Rect, default_label_style: Style) {
110        if let Some(label) = &self.label {
111            // align the label. Necessary to do it this way as we don't want to set the style
112            // of the whole area, just the label area
113            let width = label.width() as u16;
114            let area = match label.alignment {
115                Some(Alignment::Center) => Rect {
116                    x: area.x + (area.width.saturating_sub(width)) / 2,
117                    width,
118                    ..area
119                },
120                Some(Alignment::Right) => Rect {
121                    x: area.x + area.width.saturating_sub(width),
122                    width,
123                    ..area
124                },
125                _ => Rect { width, ..area },
126            };
127            buf.set_style(area, default_label_style);
128            label.render(area, buf);
129        }
130    }
131}
132
133impl<'a> From<&[(&'a str, u64)]> for BarGroup<'a> {
134    fn from(value: &[(&'a str, u64)]) -> Self {
135        Self {
136            label: None,
137            bars: value
138                .iter()
139                .map(|&(text, v)| Bar::with_label(text, v))
140                .collect(),
141        }
142    }
143}
144
145impl<'a, const N: usize> From<&[(&'a str, u64); N]> for BarGroup<'a> {
146    fn from(value: &[(&'a str, u64); N]) -> Self {
147        let value: &[(&'a str, u64)] = value.as_ref();
148        Self::from(value)
149    }
150}
151
152impl<'a> From<&Vec<(&'a str, u64)>> for BarGroup<'a> {
153    fn from(value: &Vec<(&'a str, u64)>) -> Self {
154        let array: &[(&str, u64)] = value;
155        Self::from(array)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn test_bargroup_new() {
165        let group = BarGroup::new([Bar::with_label("Label1", 1), Bar::with_label("Label2", 2)])
166            .label(Line::from("Group1"));
167        assert_eq!(group.label, Some(Line::from("Group1")));
168        assert_eq!(group.bars.len(), 2);
169    }
170}