1mod layout;
4mod node;
5mod reconcile;
6
7pub use layout::measure_chart;
8pub use node::ChartNode;
9pub use reconcile::reconcile_chart;
10
11use std::sync::Arc;
12
13use crate::core::element::{Element, ElementKind};
14use crate::style::{BorderStyle, Length, Padding, Style};
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
18pub enum ChartSeriesMode {
19 #[default]
21 Line,
22 Braille,
24 Bars,
26}
27
28#[derive(Clone, Debug)]
30pub struct ChartSeries {
31 pub(crate) name: Arc<str>,
32 pub(crate) data: Arc<[f64]>,
33 pub(crate) mode: ChartSeriesMode,
34 pub(crate) style: Style,
35 pub(crate) point_char: char,
36 pub(crate) line_char: char,
37 pub(crate) bar_char: char,
38}
39
40impl ChartSeries {
41 pub fn new(name: impl Into<Arc<str>>, data: impl IntoIterator<Item = f64>) -> Self {
43 Self {
44 name: name.into(),
45 data: data.into_iter().collect::<Vec<_>>().into(),
46 mode: ChartSeriesMode::Line,
47 style: Style::default(),
48 point_char: '●',
49 line_char: '─',
50 bar_char: '█',
51 }
52 }
53
54 pub fn data_arc(mut self, data: Arc<[f64]>) -> Self {
56 self.data = data;
57 self
58 }
59
60 pub fn mode(mut self, mode: ChartSeriesMode) -> Self {
62 self.mode = mode;
63 self
64 }
65
66 pub fn style(mut self, style: Style) -> Self {
68 self.style = style;
69 self
70 }
71
72 pub fn point_char(mut self, point_char: char) -> Self {
74 self.point_char = point_char;
75 self
76 }
77
78 pub fn line_char(mut self, line_char: char) -> Self {
80 self.line_char = line_char;
81 self
82 }
83
84 pub fn bar_char(mut self, bar_char: char) -> Self {
86 self.bar_char = bar_char;
87 self
88 }
89}
90
91#[derive(Clone, Debug)]
93pub struct ChartAxis {
94 pub(crate) show: bool,
95 pub(crate) min: Option<f64>,
96 pub(crate) max: Option<f64>,
97 pub(crate) ticks: u16,
98 pub(crate) tick_labels: Arc<[Arc<str>]>,
99 pub(crate) label: Option<Arc<str>>,
100 pub(crate) style: Style,
101}
102
103impl Default for ChartAxis {
104 fn default() -> Self {
105 Self {
106 show: true,
107 min: None,
108 max: None,
109 ticks: 4,
110 tick_labels: Arc::from([]),
111 label: None,
112 style: Style::default(),
113 }
114 }
115}
116
117impl ChartAxis {
118 pub fn new() -> Self {
120 Self::default()
121 }
122
123 pub fn show(mut self, show: bool) -> Self {
125 self.show = show;
126 self
127 }
128
129 pub fn range(mut self, min: f64, max: f64) -> Self {
131 self.min = Some(min);
132 self.max = Some(max);
133 self
134 }
135
136 pub fn ticks(mut self, ticks: u16) -> Self {
138 self.ticks = ticks.max(2);
139 self
140 }
141
142 pub fn tick_labels<S: Into<Arc<str>>>(mut self, labels: impl IntoIterator<Item = S>) -> Self {
149 self.tick_labels = labels.into_iter().map(Into::into).collect();
150 self
151 }
152
153 pub fn label(mut self, label: impl Into<Arc<str>>) -> Self {
155 self.label = Some(label.into());
156 self
157 }
158
159 pub fn style(mut self, style: Style) -> Self {
161 self.style = style;
162 self
163 }
164}
165
166#[derive(Clone, Debug)]
168pub struct ChartThreshold {
169 pub(crate) value: f64,
170 pub(crate) label: Option<Arc<str>>,
171 pub(crate) style: Style,
172 pub(crate) glyph: char,
173}
174
175impl ChartThreshold {
176 pub fn new(value: f64) -> Self {
178 Self {
179 value,
180 label: None,
181 style: Style::default(),
182 glyph: '┈',
183 }
184 }
185
186 pub fn label(mut self, label: impl Into<Arc<str>>) -> Self {
188 self.label = Some(label.into());
189 self
190 }
191
192 pub fn style(mut self, style: Style) -> Self {
194 self.style = style;
195 self
196 }
197
198 pub fn glyph(mut self, glyph: char) -> Self {
200 self.glyph = glyph;
201 self
202 }
203}
204
205#[derive(Clone)]
207pub struct Chart {
208 pub(crate) series: Arc<[ChartSeries]>,
209 pub(crate) thresholds: Arc<[ChartThreshold]>,
210 pub(crate) x_axis: ChartAxis,
211 pub(crate) y_axis: ChartAxis,
212 pub(crate) style: Style,
213 pub(crate) axis_style: Style,
214 pub(crate) grid_style: Style,
215 pub(crate) legend_style: Style,
216 pub(crate) show_grid: bool,
217 pub(crate) show_legend: bool,
218 pub(crate) legend_separator: Arc<str>,
219 pub(crate) viewport_start: usize,
220 pub(crate) viewport_len: Option<usize>,
221 pub(crate) padding: Padding,
224 pub(crate) border: bool,
225 pub(crate) border_style: BorderStyle,
228 pub(crate) width: Length,
231 pub(crate) height: Length,
234}
235
236impl Default for Chart {
237 fn default() -> Self {
238 Self {
239 series: Arc::new([]),
240 thresholds: Arc::new([]),
241 x_axis: ChartAxis::default(),
242 y_axis: ChartAxis::default(),
243 style: Style::default(),
244 axis_style: Style::default(),
245 grid_style: Style::default(),
246 legend_style: Style::default(),
247 show_grid: true,
248 show_legend: true,
249 legend_separator: Arc::from(" "),
250 viewport_start: 0,
251 viewport_len: None,
252 padding: Padding::default(),
253 border: false,
254 border_style: BorderStyle::Plain,
255 width: Length::Flex(1),
256 height: Length::Px(10),
257 }
258 }
259}
260
261impl Chart {
262 pub fn new() -> Self {
264 Self::default()
265 }
266
267 pub fn series(mut self, series: impl IntoIterator<Item = ChartSeries>) -> Self {
269 self.series = series.into_iter().collect::<Vec<_>>().into();
270 self
271 }
272
273 pub fn series_arc(mut self, series: Arc<[ChartSeries]>) -> Self {
275 self.series = series;
276 self
277 }
278
279 pub fn add_series(mut self, series: ChartSeries) -> Self {
281 let mut next = self.series.to_vec();
282 next.push(series);
283 self.series = next.into();
284 self
285 }
286
287 pub fn thresholds(mut self, thresholds: impl IntoIterator<Item = ChartThreshold>) -> Self {
289 self.thresholds = thresholds.into_iter().collect::<Vec<_>>().into();
290 self
291 }
292
293 pub fn x_axis(mut self, axis: ChartAxis) -> Self {
295 self.x_axis = axis;
296 self
297 }
298
299 pub fn y_axis(mut self, axis: ChartAxis) -> Self {
301 self.y_axis = axis;
302 self
303 }
304
305 pub fn style(mut self, style: Style) -> Self {
307 self.style = style;
308 self
309 }
310
311 pub fn axis_style(mut self, style: Style) -> Self {
313 self.axis_style = style;
314 self
315 }
316
317 pub fn grid_style(mut self, style: Style) -> Self {
319 self.grid_style = style;
320 self
321 }
322
323 pub fn legend_style(mut self, style: Style) -> Self {
325 self.legend_style = style;
326 self
327 }
328
329 pub fn show_grid(mut self, show_grid: bool) -> Self {
331 self.show_grid = show_grid;
332 self
333 }
334
335 pub fn show_legend(mut self, show_legend: bool) -> Self {
337 self.show_legend = show_legend;
338 self
339 }
340
341 pub fn legend_separator(mut self, legend_separator: impl Into<Arc<str>>) -> Self {
343 self.legend_separator = legend_separator.into();
344 self
345 }
346
347 pub fn viewport_start(mut self, viewport_start: usize) -> Self {
349 self.viewport_start = viewport_start;
350 self
351 }
352
353 pub fn viewport_len(mut self, viewport_len: Option<usize>) -> Self {
355 self.viewport_len = viewport_len;
356 self
357 }
358
359 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
361 self.padding = padding.into();
362 self
363 }
364
365 pub fn border(mut self, border: bool) -> Self {
367 self.border = border;
368 self
369 }
370
371 pub fn border_style(mut self, border_style: BorderStyle) -> Self {
373 self.border_style = border_style;
374 self
375 }
376
377 pub fn width(mut self, width: Length) -> Self {
379 self.width = width;
380 self
381 }
382
383 pub fn height(mut self, height: Length) -> Self {
385 self.height = height;
386 self
387 }
388}
389
390impl From<Chart> for Element {
391 fn from(value: Chart) -> Self {
392 Element::new(ElementKind::Chart(Box::new(value)))
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use std::sync::Arc;
400
401 #[test]
402 fn series_arc_preserves_shared_slice() {
403 let series: Arc<[ChartSeries]> = Arc::from([ChartSeries::new("cpu", [1.0, 2.0, 3.0])]);
404 let chart = Chart::new().series_arc(Arc::clone(&series));
405 assert!(Arc::ptr_eq(&chart.series, &series));
406 }
407
408 #[test]
409 fn chart_series_data_arc_preserves_shared_slice() {
410 let data: Arc<[f64]> = Arc::from([1.0, 2.0, 3.0]);
411 let series = ChartSeries::new("cpu", []).data_arc(Arc::clone(&data));
412 assert!(Arc::ptr_eq(&series.data, &data));
413 }
414}