Skip to main content

esoc_chart/
axes.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Axes: a single plot area with axis config, series, and legend.
3
4use esoc_gfx::color::Color;
5use esoc_gfx::style::DashPattern;
6
7use crate::axis::AxisConfig;
8use crate::chart::{
9    BarSeries, BoxPlotSeries, ErrorBarSeries, HeatmapSeries, HistogramSeries, LineSeries,
10    ScatterSeries,
11};
12use crate::legend::LegendPosition;
13use crate::series::SeriesRenderer;
14
15/// A single plot area containing axes, series, and legend configuration.
16#[derive(Default)]
17pub struct Axes {
18    /// Title for this plot area.
19    pub title: Option<String>,
20    /// X-axis configuration.
21    pub x_config: AxisConfig,
22    /// Y-axis configuration.
23    pub y_config: AxisConfig,
24    /// Data series to render.
25    pub(crate) series: Vec<Box<dyn SeriesRenderer>>,
26    /// Legend position.
27    pub legend_position: LegendPosition,
28    /// Whether to show the legend.
29    pub show_legend: bool,
30}
31
32impl Axes {
33    /// Create a new axes.
34    pub fn new() -> Self {
35        Self {
36            show_legend: true,
37            ..Self::default()
38        }
39    }
40
41    /// Set the plot title.
42    pub fn title(&mut self, title: impl Into<String>) -> &mut Self {
43        self.title = Some(title.into());
44        self
45    }
46
47    /// Set the X-axis label.
48    pub fn x_label(&mut self, label: impl Into<String>) -> &mut Self {
49        self.x_config.label = Some(label.into());
50        self
51    }
52
53    /// Set the Y-axis label.
54    pub fn y_label(&mut self, label: impl Into<String>) -> &mut Self {
55        self.y_config.label = Some(label.into());
56        self
57    }
58
59    /// Set the X-axis range manually.
60    pub fn x_range(&mut self, min: f64, max: f64) -> &mut Self {
61        self.x_config.range = Some((min, max));
62        self
63    }
64
65    /// Set the Y-axis range manually.
66    pub fn y_range(&mut self, min: f64, max: f64) -> &mut Self {
67        self.y_config.range = Some((min, max));
68        self
69    }
70
71    /// Set X-axis config.
72    pub fn x_axis(&mut self, config: AxisConfig) -> &mut Self {
73        self.x_config = config;
74        self
75    }
76
77    /// Set Y-axis config.
78    pub fn y_axis(&mut self, config: AxisConfig) -> &mut Self {
79        self.y_config = config;
80        self
81    }
82
83    /// Set legend position.
84    pub fn legend(&mut self, position: LegendPosition) -> &mut Self {
85        self.legend_position = position;
86        self
87    }
88
89    /// Add a line series and return a builder for customization.
90    pub fn line(&mut self, x: &[f64], y: &[f64]) -> LineBuilder<'_> {
91        LineBuilder {
92            axes: self,
93            series: Some(LineSeries::new(x, y)),
94        }
95    }
96
97    /// Add a scatter series and return a builder for customization.
98    pub fn scatter(&mut self, x: &[f64], y: &[f64]) -> ScatterBuilder<'_> {
99        ScatterBuilder {
100            axes: self,
101            series: Some(ScatterSeries::new(x, y)),
102        }
103    }
104
105    /// Add a bar series and return a builder for customization.
106    pub fn bar(&mut self, x: &[f64], heights: &[f64]) -> BarBuilder<'_> {
107        BarBuilder {
108            axes: self,
109            series: Some(BarSeries::new(x, heights)),
110        }
111    }
112
113    /// Add a histogram series and return a builder for customization.
114    pub fn histogram(&mut self, data: &[f64]) -> HistogramBuilder<'_> {
115        HistogramBuilder {
116            axes: self,
117            series: Some(HistogramSeries::new(data)),
118        }
119    }
120
121    /// Add a box plot series and return a builder for customization.
122    pub fn boxplot(&mut self, datasets: Vec<Vec<f64>>) -> BoxPlotBuilder<'_> {
123        BoxPlotBuilder {
124            axes: self,
125            series: Some(BoxPlotSeries::new(datasets)),
126        }
127    }
128
129    /// Add a heatmap series and return a builder for customization.
130    pub fn heatmap(&mut self, data: Vec<Vec<f64>>) -> HeatmapBuilder<'_> {
131        HeatmapBuilder {
132            axes: self,
133            series: Some(HeatmapSeries::new(data)),
134        }
135    }
136
137    /// Add an error bar series and return a builder for customization.
138    pub fn errorbar(&mut self, x: &[f64], y: &[f64], err: &[f64]) -> ErrorBarBuilder<'_> {
139        ErrorBarBuilder {
140            axes: self,
141            series: Some(ErrorBarSeries::new(x, y, err)),
142        }
143    }
144
145    /// Add an arbitrary series.
146    pub fn add_series(&mut self, series: Box<dyn SeriesRenderer>) -> &mut Self {
147        self.series.push(series);
148        self
149    }
150}
151
152// --- Series builders with fluent API ---
153// Each builder uses Option<Series> so Drop can take() the series
154// and done() can also take() it, preventing double-add.
155
156/// Builder for a line series.
157pub struct LineBuilder<'a> {
158    axes: &'a mut Axes,
159    series: Option<LineSeries>,
160}
161
162impl<'a> LineBuilder<'a> {
163    /// Set the series label.
164    pub fn label(mut self, label: impl Into<String>) -> Self {
165        if let Some(s) = &mut self.series {
166            s.label = Some(label.into());
167        }
168        self
169    }
170
171    /// Set the line color.
172    pub fn color(mut self, color: Color) -> Self {
173        if let Some(s) = &mut self.series {
174            s.color = Some(color);
175        }
176        self
177    }
178
179    /// Set the line width.
180    pub fn width(mut self, width: f64) -> Self {
181        if let Some(s) = &mut self.series {
182            s.width = Some(width);
183        }
184        self
185    }
186
187    /// Set a dash pattern.
188    pub fn dash(mut self, dashes: &[f64]) -> Self {
189        if let Some(s) = &mut self.series {
190            s.dash = Some(DashPattern::new(dashes));
191        }
192        self
193    }
194
195    /// Finish and add the series.
196    pub fn done(mut self) -> &'a mut Axes {
197        if let Some(s) = self.series.take() {
198            self.axes.series.push(Box::new(s));
199        }
200        self.axes
201    }
202}
203
204/// Builder for a scatter series.
205pub struct ScatterBuilder<'a> {
206    axes: &'a mut Axes,
207    series: Option<ScatterSeries>,
208}
209
210impl<'a> ScatterBuilder<'a> {
211    /// Set the series label.
212    pub fn label(mut self, label: impl Into<String>) -> Self {
213        if let Some(s) = &mut self.series {
214            s.label = Some(label.into());
215        }
216        self
217    }
218
219    /// Set the point color.
220    pub fn color(mut self, color: Color) -> Self {
221        if let Some(s) = &mut self.series {
222            s.color = Some(color);
223        }
224        self
225    }
226
227    /// Set the point radius.
228    pub fn radius(mut self, r: f64) -> Self {
229        if let Some(s) = &mut self.series {
230            s.radius = Some(r);
231        }
232        self
233    }
234
235    /// Finish and add the series.
236    pub fn done(mut self) -> &'a mut Axes {
237        if let Some(s) = self.series.take() {
238            self.axes.series.push(Box::new(s));
239        }
240        self.axes
241    }
242}
243
244/// Builder for a bar series.
245pub struct BarBuilder<'a> {
246    axes: &'a mut Axes,
247    series: Option<BarSeries>,
248}
249
250impl<'a> BarBuilder<'a> {
251    /// Set the series label.
252    pub fn label(mut self, label: impl Into<String>) -> Self {
253        if let Some(s) = &mut self.series {
254            s.label = Some(label.into());
255        }
256        self
257    }
258
259    /// Set bar color.
260    pub fn color(mut self, color: Color) -> Self {
261        if let Some(s) = &mut self.series {
262            s.color = Some(color);
263        }
264        self
265    }
266
267    /// Set bar width.
268    pub fn bar_width(mut self, width: f64) -> Self {
269        if let Some(s) = &mut self.series {
270            s.bar_width = width;
271        }
272        self
273    }
274
275    /// Use horizontal bars.
276    pub fn horizontal(mut self) -> Self {
277        if let Some(s) = &mut self.series {
278            s.horizontal = true;
279        }
280        self
281    }
282
283    /// Finish and add the series.
284    pub fn done(mut self) -> &'a mut Axes {
285        if let Some(s) = self.series.take() {
286            self.axes.series.push(Box::new(s));
287        }
288        self.axes
289    }
290}
291
292/// Builder for a histogram series.
293pub struct HistogramBuilder<'a> {
294    axes: &'a mut Axes,
295    series: Option<HistogramSeries>,
296}
297
298impl<'a> HistogramBuilder<'a> {
299    /// Set the series label.
300    pub fn label(mut self, label: impl Into<String>) -> Self {
301        if let Some(s) = &mut self.series {
302            s.label = Some(label.into());
303        }
304        self
305    }
306
307    /// Set bar color.
308    pub fn color(mut self, color: Color) -> Self {
309        if let Some(s) = &mut self.series {
310            s.color = Some(color);
311        }
312        self
313    }
314
315    /// Set the number of bins.
316    pub fn bins(mut self, bins: usize) -> Self {
317        if let Some(s) = &mut self.series {
318            s.bin_count = Some(bins);
319        }
320        self
321    }
322
323    /// Finish and add the series.
324    pub fn done(mut self) -> &'a mut Axes {
325        if let Some(s) = self.series.take() {
326            self.axes.series.push(Box::new(s));
327        }
328        self.axes
329    }
330}
331
332/// Builder for a box plot series.
333pub struct BoxPlotBuilder<'a> {
334    axes: &'a mut Axes,
335    series: Option<BoxPlotSeries>,
336}
337
338impl<'a> BoxPlotBuilder<'a> {
339    /// Set the series label.
340    pub fn label(mut self, label: impl Into<String>) -> Self {
341        if let Some(s) = &mut self.series {
342            s.label = Some(label.into());
343        }
344        self
345    }
346
347    /// Set category labels.
348    pub fn labels(mut self, labels: Vec<String>) -> Self {
349        if let Some(s) = &mut self.series {
350            s.labels = Some(labels);
351        }
352        self
353    }
354
355    /// Finish and add the series.
356    pub fn done(mut self) -> &'a mut Axes {
357        if let Some(s) = self.series.take() {
358            self.axes.series.push(Box::new(s));
359        }
360        self.axes
361    }
362}
363
364/// Builder for a heatmap series.
365pub struct HeatmapBuilder<'a> {
366    axes: &'a mut Axes,
367    series: Option<HeatmapSeries>,
368}
369
370impl<'a> HeatmapBuilder<'a> {
371    /// Set the series label.
372    pub fn label(mut self, label: impl Into<String>) -> Self {
373        if let Some(s) = &mut self.series {
374            s.label = Some(label.into());
375        }
376        self
377    }
378
379    /// Enable cell value annotations.
380    pub fn annotate(mut self) -> Self {
381        if let Some(s) = &mut self.series {
382            s.annotate = true;
383        }
384        self
385    }
386
387    /// Set row labels.
388    pub fn row_labels(mut self, labels: Vec<String>) -> Self {
389        if let Some(s) = &mut self.series {
390            s.row_labels = Some(labels);
391        }
392        self
393    }
394
395    /// Set column labels.
396    pub fn col_labels(mut self, labels: Vec<String>) -> Self {
397        if let Some(s) = &mut self.series {
398            s.col_labels = Some(labels);
399        }
400        self
401    }
402
403    /// Finish and add the series.
404    pub fn done(mut self) -> &'a mut Axes {
405        if let Some(s) = self.series.take() {
406            self.axes.series.push(Box::new(s));
407        }
408        self.axes
409    }
410}
411
412/// Builder for an error bar series.
413pub struct ErrorBarBuilder<'a> {
414    axes: &'a mut Axes,
415    series: Option<ErrorBarSeries>,
416}
417
418impl<'a> ErrorBarBuilder<'a> {
419    /// Set the series label.
420    pub fn label(mut self, label: impl Into<String>) -> Self {
421        if let Some(s) = &mut self.series {
422            s.label = Some(label.into());
423        }
424        self
425    }
426
427    /// Set line color.
428    pub fn color(mut self, color: Color) -> Self {
429        if let Some(s) = &mut self.series {
430            s.color = Some(color);
431        }
432        self
433    }
434
435    /// Finish and add the series.
436    pub fn done(mut self) -> &'a mut Axes {
437        if let Some(s) = self.series.take() {
438            self.axes.series.push(Box::new(s));
439        }
440        self.axes
441    }
442}