ratatui/widgets/barchart/
bar.rs

1use unicode_width::UnicodeWidthStr;
2
3use crate::{buffer::Buffer, layout::Rect, style::Style, text::Line, widgets::Widget};
4/// A bar to be shown by the [`BarChart`](crate::widgets::BarChart) widget.
5///
6/// Here is an explanation of a `Bar`'s components.
7/// ```plain
8/// ███                          ┐
9/// █2█  <- text_value or value  │ bar
10/// foo  <- label                ┘
11/// ```
12/// Note that every element can be styled individually.
13///
14/// # Example
15///
16/// The following example creates a bar with the label "Bar 1", a value "10",
17/// red background and a white value foreground.
18/// ```
19/// use ratatui::{
20///     style::{Style, Stylize},
21///     widgets::Bar,
22/// };
23///
24/// Bar::default()
25///     .label("Bar 1".into())
26///     .value(10)
27///     .style(Style::new().red())
28///     .value_style(Style::new().red().on_white())
29///     .text_value("10°C".to_string());
30/// ```
31#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
32pub struct Bar<'a> {
33    /// Value to display on the bar (computed when the data is passed to the widget)
34    pub(super) value: u64,
35    /// optional label to be printed under the bar
36    pub(super) label: Option<Line<'a>>,
37    /// style for the bar
38    pub(super) style: Style,
39    /// style of the value printed at the bottom of the bar.
40    pub(super) value_style: Style,
41    /// optional `text_value` to be shown on the bar instead of the actual value
42    pub(super) text_value: Option<String>,
43}
44
45impl<'a> Bar<'a> {
46    /// Set the value of this bar.
47    ///
48    /// The value will be displayed inside the bar.
49    ///
50    /// # See also
51    ///
52    /// [`Bar::value_style`] to style the value.
53    /// [`Bar::text_value`] to set the displayed value.
54    #[must_use = "method moves the value of self and returns the modified value"]
55    pub const fn value(mut self, value: u64) -> Self {
56        self.value = value;
57        self
58    }
59
60    /// Set the label of the bar.
61    ///
62    /// For [`Vertical`](crate::layout::Direction::Vertical) bars,
63    /// display the label **under** the bar.
64    /// For [`Horizontal`](crate::layout::Direction::Horizontal) bars,
65    /// display the label **in** the bar.
66    /// See [`BarChart::direction`](crate::widgets::BarChart::direction) to set the direction.
67    #[must_use = "method moves the value of self and returns the modified value"]
68    pub fn label(mut self, label: Line<'a>) -> Self {
69        self.label = Some(label);
70        self
71    }
72
73    /// Set the style of the bar.
74    ///
75    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
76    /// your own type that implements [`Into<Style>`]).
77    ///
78    /// This will apply to every non-styled element. It can be seen and used as a default value.
79    ///
80    /// [`Color`]: crate::style::Color
81    #[must_use = "method moves the value of self and returns the modified value"]
82    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
83        self.style = style.into();
84        self
85    }
86
87    /// Set the style of the value.
88    ///
89    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
90    /// your own type that implements [`Into<Style>`]).
91    ///
92    /// # See also
93    ///
94    /// [`Bar::value`] to set the value.
95    ///
96    /// [`Color`]: crate::style::Color
97    #[must_use = "method moves the value of self and returns the modified value"]
98    pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
99        self.value_style = style.into();
100        self
101    }
102
103    /// Set the text value printed in the bar.
104    ///
105    /// If `text_value` is not set, then the [`ToString`] representation of `value` will be shown on
106    /// the bar.
107    ///
108    /// # See also
109    ///
110    /// [`Bar::value`] to set the value.
111    #[must_use = "method moves the value of self and returns the modified value"]
112    pub fn text_value(mut self, text_value: String) -> Self {
113        self.text_value = Some(text_value);
114        self
115    }
116
117    /// Render the value of the bar.
118    ///
119    /// [`text_value`](Bar::text_value) is used if set, otherwise the value is converted to string.
120    /// The value is rendered using `value_style`. If the value width is greater than the
121    /// bar width, then the value is split into 2 parts. the first part is rendered in the bar
122    /// using `value_style`. The second part is rendered outside the bar using `bar_style`
123    pub(super) fn render_value_with_different_styles(
124        &self,
125        buf: &mut Buffer,
126        area: Rect,
127        bar_length: usize,
128        default_value_style: Style,
129        bar_style: Style,
130    ) {
131        let value = self.value.to_string();
132        let text = self.text_value.as_ref().unwrap_or(&value);
133
134        if !text.is_empty() {
135            let style = default_value_style.patch(self.value_style);
136            // Since the value may be longer than the bar itself, we need to use 2 different styles
137            // while rendering. Render the first part with the default value style
138            buf.set_stringn(area.x, area.y, text, bar_length, style);
139            // render the second part with the bar_style
140            if text.len() > bar_length {
141                let (first, second) = text.split_at(bar_length);
142
143                let style = bar_style.patch(self.style);
144                buf.set_stringn(
145                    area.x + first.len() as u16,
146                    area.y,
147                    second,
148                    area.width as usize - first.len(),
149                    style,
150                );
151            }
152        }
153    }
154
155    pub(super) fn render_value(
156        &self,
157        buf: &mut Buffer,
158        max_width: u16,
159        x: u16,
160        y: u16,
161        default_value_style: Style,
162        ticks: u64,
163    ) {
164        if self.value != 0 {
165            const TICKS_PER_LINE: u64 = 8;
166            let value = self.value.to_string();
167            let value_label = self.text_value.as_ref().unwrap_or(&value);
168            let width = value_label.width() as u16;
169            // if we have enough space or the ticks are greater equal than 1 cell (8)
170            // then print the value
171            if width < max_width || (width == max_width && ticks >= TICKS_PER_LINE) {
172                buf.set_string(
173                    x + (max_width.saturating_sub(value_label.len() as u16) >> 1),
174                    y,
175                    value_label,
176                    default_value_style.patch(self.value_style),
177                );
178            }
179        }
180    }
181
182    pub(super) fn render_label(
183        &self,
184        buf: &mut Buffer,
185        max_width: u16,
186        x: u16,
187        y: u16,
188        default_label_style: Style,
189    ) {
190        // center the label. Necessary to do it this way as we don't want to set the style
191        // of the whole area, just the label area
192        let width = self
193            .label
194            .as_ref()
195            .map_or(0, Line::width)
196            .min(max_width as usize) as u16;
197        let area = Rect {
198            x: x + (max_width.saturating_sub(width)) / 2,
199            y,
200            width,
201            height: 1,
202        };
203        buf.set_style(area, default_label_style);
204        if let Some(label) = &self.label {
205            label.render(area, buf);
206        }
207    }
208}