Skip to main content

oino_ui/widgets/
barchart.rs

1//! ratatui `BarChart` (§4.5.1).
2
3use serde::{Deserialize, Serialize};
4
5use crate::layout::Direction;
6use crate::node::{ActionId, Common, IntoNode, Node};
7use crate::style::Style;
8use crate::text::Line;
9
10/// One bar of a group.
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct Bar {
14    /// Bar height.
15    pub value: u64,
16    /// Bar label.
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub label: Option<Line>,
19    /// Text printed over the bar instead of the value.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub text_value: Option<String>,
22    /// Bar style.
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub style: Option<Style>,
25    /// Value-label style.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub value_style: Option<Style>,
28}
29
30impl Bar {
31    /// A bar of the given height.
32    #[must_use]
33    pub fn new(value: u64) -> Self {
34        Self {
35            value,
36            label: None,
37            text_value: None,
38            style: None,
39            value_style: None,
40        }
41    }
42
43    /// Sets the bar label.
44    #[must_use]
45    pub fn label(mut self, label: impl Into<Line>) -> Self {
46        self.label = Some(label.into());
47        self
48    }
49
50    /// Sets the text printed over the bar.
51    #[must_use]
52    pub fn text_value(mut self, text: impl Into<String>) -> Self {
53        self.text_value = Some(text.into());
54        self
55    }
56
57    /// Sets the bar style.
58    #[must_use]
59    pub fn style(mut self, style: Style) -> Self {
60        self.style = Some(style);
61        self
62    }
63
64    /// Sets the value-label style.
65    #[must_use]
66    pub fn value_style(mut self, style: Style) -> Self {
67        self.value_style = Some(style);
68        self
69    }
70}
71
72/// A group of bars with an optional group label.
73#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct BarGroup {
76    /// Group label.
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub label: Option<Line>,
79    /// Bars of the group.
80    pub bars: Vec<Bar>,
81}
82
83impl BarGroup {
84    /// A group from bars.
85    #[must_use]
86    pub fn new(bars: impl IntoIterator<Item = Bar>) -> Self {
87        Self {
88            label: None,
89            bars: bars.into_iter().collect(),
90        }
91    }
92
93    /// Sets the group label.
94    #[must_use]
95    pub fn label(mut self, label: impl Into<Line>) -> Self {
96        self.label = Some(label.into());
97        self
98    }
99}
100
101/// ratatui `BarChart` (§4.5.1). Actions report `"<groupIndex>:<barIndex>"` (§4.5.1).
102#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct BarChart {
105    /// Common node props (`key`, `style`, `block`, `fallback`).
106    #[serde(flatten)]
107    pub common: Common,
108    /// Bar groups.
109    pub data: Vec<BarGroup>,
110    /// Bar width in columns (default 1).
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub bar_width: Option<u16>,
113    /// Gap between bars of a group (default 1).
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub bar_gap: Option<u16>,
116    /// Gap between groups (default 0).
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub group_gap: Option<u16>,
119    /// Orientation.
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub direction: Option<Direction>,
122    /// Maximum value (auto from data when absent).
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub max: Option<u64>,
125    /// Default bar style.
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub bar_style: Option<Style>,
128    /// Default value-label style.
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub value_style: Option<Style>,
131    /// Default label style.
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub label_style: Option<Style>,
134    /// GUI extra: action on bar select.
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub on_select: Option<ActionId>,
137}
138
139impl BarChart {
140    /// A chart from groups.
141    #[must_use]
142    pub fn new(data: impl IntoIterator<Item = BarGroup>) -> Self {
143        Self {
144            data: data.into_iter().collect(),
145            ..Self::default()
146        }
147    }
148
149    /// Convenience: one group from `(label, value)` bars.
150    #[must_use]
151    pub fn from_pairs(pairs: impl IntoIterator<Item = (impl Into<Line>, u64)>) -> Self {
152        Self::new(vec![BarGroup::new(
153            pairs
154                .into_iter()
155                .map(|(label, value)| Bar::new(value).label(label)),
156        )])
157    }
158
159    /// Sets the bar width.
160    #[must_use]
161    pub fn bar_width(mut self, width: u16) -> Self {
162        self.bar_width = Some(width);
163        self
164    }
165
166    /// Sets the gap between bars of a group.
167    #[must_use]
168    pub fn bar_gap(mut self, gap: u16) -> Self {
169        self.bar_gap = Some(gap);
170        self
171    }
172
173    /// Sets the gap between groups.
174    #[must_use]
175    pub fn group_gap(mut self, gap: u16) -> Self {
176        self.group_gap = Some(gap);
177        self
178    }
179
180    /// Sets the orientation.
181    #[must_use]
182    pub fn direction(mut self, direction: Direction) -> Self {
183        self.direction = Some(direction);
184        self
185    }
186
187    /// Sets the maximum value.
188    #[must_use]
189    pub fn max(mut self, max: u64) -> Self {
190        self.max = Some(max);
191        self
192    }
193
194    /// Sets the default bar style.
195    #[must_use]
196    pub fn bar_style(mut self, style: Style) -> Self {
197        self.bar_style = Some(style);
198        self
199    }
200
201    /// Sets the default value-label style.
202    #[must_use]
203    pub fn value_style(mut self, style: Style) -> Self {
204        self.value_style = Some(style);
205        self
206    }
207
208    /// Sets the default label style.
209    #[must_use]
210    pub fn label_style(mut self, style: Style) -> Self {
211        self.label_style = Some(style);
212        self
213    }
214
215    /// Sets the select action.
216    #[must_use]
217    pub fn on_select(mut self, action: impl Into<ActionId>) -> Self {
218        self.on_select = Some(action.into());
219        self
220    }
221}
222
223impl IntoNode for BarChart {}
224impl From<BarChart> for Node {
225    fn from(c: BarChart) -> Self {
226        Node::BarChart(c)
227    }
228}