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}