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