embedded_charts/
fluent.rs

1//! Fluent API for creating charts with an intuitive, readable syntax.
2//!
3//! This module provides a streamlined interface for creating charts that reduces
4//! boilerplate and makes common chart configurations more accessible. The fluent
5//! API maintains all the performance and memory characteristics of the core library
6//! while providing a more developer-friendly interface.
7//!
8//! # Examples
9//!
10//! ## Simple Line Chart
11//! ```rust
12//! # #[cfg(feature = "line")]
13//! # {
14//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
15//! use embedded_charts::fluent::Chart;
16//! use embedded_charts::prelude::*;
17//! use embedded_graphics::pixelcolor::Rgb565;
18//!
19//! let data = [(0.0, 10.0), (1.0, 20.0), (2.0, 15.0)];
20//! let chart = Chart::line()
21//!     .data_from_tuples(&data)
22//!     .color(Rgb565::BLUE)
23//!     .title("Temperature")
24//!     .build()?;
25//! # Ok(())
26//! # }
27//! # }
28//! ```
29//!
30//! ## Professional Styled Chart
31//! ```rust,no_run
32//! # #[cfg(feature = "line")]
33//! # {
34//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
35//! use embedded_charts::fluent::Chart;
36//! use embedded_charts::prelude::*;
37//! use embedded_graphics::pixelcolor::Rgb565;
38//!
39//! let chart: LineChart<Rgb565> = Chart::line()
40//!     .preset(ChartPreset::Professional)
41//!     .data_from_tuples(&[(0.0, 10.0), (1.0, 20.0)])
42//!     .title("Sales Data")
43//!     .build()?;
44//! # Ok(())
45//! # }
46//! # }
47//! ```
48//!
49//! ## Multi-Series Chart
50//! ```rust,no_run
51//! # #[cfg(feature = "line")]
52//! # {
53//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
54//! use embedded_charts::fluent::Chart;
55//! use embedded_charts::prelude::*;
56//! use embedded_graphics::pixelcolor::Rgb565;
57//!
58//! let chart: LineChart<Rgb565> = Chart::line()
59//!     .series("Temperature", &[(0.0, 22.5), (1.0, 23.1)])
60//!     .series("Humidity", &[(0.0, 65.0), (1.0, 68.0)])
61//!     .color(Rgb565::BLUE)
62//!     .build()?;
63//! # Ok(())
64//! # }
65//! # }
66//! ```
67
68#[cfg(any(feature = "line", feature = "bar"))]
69use crate::chart::traits::ChartBuilder;
70#[allow(unused_imports)]
71use crate::data::MultiSeries;
72#[cfg(any(feature = "line", feature = "bar"))]
73use crate::data::{Point2D, StaticDataSeries};
74#[cfg(any(feature = "line", feature = "bar"))]
75use crate::error::ChartResult;
76#[cfg(any(feature = "line", feature = "bar"))]
77use embedded_graphics::prelude::*;
78#[cfg(any(feature = "line", feature = "bar"))]
79use heapless::String;
80
81/// Chart presets for common styling patterns
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum ChartPreset {
84    /// Clean, minimal styling suitable for dashboards
85    Professional,
86    /// High contrast colors for embedded displays
87    Embedded,
88    /// Colorful, vibrant styling
89    Vibrant,
90    /// Subtle, pastel colors
91    Pastel,
92    /// Dark theme for low-light environments
93    Dark,
94}
95
96/// Fluent API builder for creating charts
97pub struct Chart;
98
99impl Chart {
100    /// Start building a line chart
101    #[cfg(feature = "line")]
102    pub fn line<C>() -> FluentLineChartBuilder<C>
103    where
104        C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565> + 'static,
105    {
106        FluentLineChartBuilder::new()
107    }
108
109    /// Start building a bar chart
110    #[cfg(feature = "bar")]
111    pub fn bar<C>() -> FluentBarChartBuilder<C>
112    where
113        C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565> + 'static,
114    {
115        FluentBarChartBuilder::new()
116    }
117}
118
119/// Fluent builder for line charts
120#[cfg(feature = "line")]
121pub struct FluentLineChartBuilder<C: PixelColor> {
122    data: Option<StaticDataSeries<Point2D, 256>>,
123    multi_data: Option<MultiSeries<Point2D, 8, 256>>,
124    color: Option<C>,
125    title: Option<String<64>>,
126    preset: Option<ChartPreset>,
127    line_width: Option<u32>,
128    show_markers: bool,
129    marker_size: Option<u32>,
130}
131
132#[cfg(feature = "line")]
133impl<C: PixelColor + 'static> FluentLineChartBuilder<C>
134where
135    C: From<embedded_graphics::pixelcolor::Rgb565>,
136{
137    fn new() -> Self {
138        Self {
139            data: None,
140            multi_data: None,
141            color: None,
142            title: None,
143            preset: None,
144            line_width: None,
145            show_markers: false,
146            marker_size: None,
147        }
148    }
149
150    /// Set data from an array of tuples
151    pub fn data_from_tuples(mut self, tuples: &[(f32, f32)]) -> Self {
152        let series =
153            StaticDataSeries::from_tuples(tuples).unwrap_or_else(|_| StaticDataSeries::new());
154        self.data = Some(series);
155        self
156    }
157
158    /// Add a data series with a label
159    pub fn series(mut self, label: &str, tuples: &[(f32, f32)]) -> Self {
160        if self.multi_data.is_none() {
161            self.multi_data = Some(MultiSeries::new());
162        }
163
164        if let Some(ref mut multi_data) = self.multi_data {
165            let mut series = StaticDataSeries::with_label(label);
166            for &(x, y) in tuples {
167                if series.push(Point2D::new(x, y)).is_err() {
168                    break; // Stop if buffer is full
169                }
170            }
171            let _ = multi_data.add_series(series);
172        }
173        self
174    }
175
176    /// Set the line color
177    pub fn color(mut self, color: C) -> Self {
178        self.color = Some(color);
179        self
180    }
181
182    /// Set the chart title
183    pub fn title(mut self, title: &str) -> Self {
184        if let Ok(title_string) = String::try_from(title) {
185            self.title = Some(title_string);
186        }
187        self
188    }
189
190    /// Apply a preset style
191    pub fn preset(mut self, preset: ChartPreset) -> Self {
192        self.preset = Some(preset);
193        self
194    }
195
196    /// Set line width
197    pub fn line_width(mut self, width: u32) -> Self {
198        self.line_width = Some(width);
199        self
200    }
201
202    /// Enable markers on data points
203    pub fn with_markers(mut self) -> Self {
204        self.show_markers = true;
205        self
206    }
207
208    /// Set marker size
209    pub fn marker_size(mut self, size: u32) -> Self {
210        self.marker_size = Some(size);
211        self.show_markers = true;
212        self
213    }
214
215    /// Build the line chart
216    pub fn build(self) -> ChartResult<crate::chart::LineChart<C>> {
217        let mut builder = crate::chart::LineChart::builder();
218
219        // Apply preset styling first
220        if let Some(preset) = self.preset {
221            builder = self.apply_preset_to_line_builder(builder, preset);
222        }
223
224        // Apply specific customizations
225        if let Some(color) = self.color {
226            builder = builder.line_color(color);
227        }
228
229        if let Some(width) = self.line_width {
230            builder = builder.line_width(width);
231        }
232
233        if self.show_markers {
234            let marker_style = crate::chart::MarkerStyle {
235                shape: crate::chart::MarkerShape::Circle,
236                size: self.marker_size.unwrap_or(4),
237                color: self
238                    .color
239                    .unwrap_or_else(|| C::from(embedded_graphics::pixelcolor::Rgb565::BLUE)),
240                visible: true,
241            };
242            builder = builder.with_markers(marker_style);
243        }
244
245        builder.build()
246    }
247
248    fn apply_preset_to_line_builder(
249        &self,
250        mut builder: crate::chart::LineChartBuilder<C>,
251        preset: ChartPreset,
252    ) -> crate::chart::LineChartBuilder<C> {
253        match preset {
254            ChartPreset::Professional => {
255                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
256                    70 >> 3,
257                    130 >> 2,
258                    180 >> 3,
259                ));
260                builder = builder.line_color(color).line_width(2);
261            }
262            ChartPreset::Embedded => {
263                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(0, 31, 0)); // Bright green
264                builder = builder.line_color(color).line_width(1);
265            }
266            ChartPreset::Vibrant => {
267                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
268                    236 >> 3,
269                    72 >> 2,
270                    153 >> 3,
271                ));
272                builder = builder.line_color(color).line_width(3);
273            }
274            ChartPreset::Pastel => {
275                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
276                    147 >> 3,
277                    197 >> 2,
278                    253 >> 3,
279                ));
280                builder = builder.line_color(color).line_width(2);
281            }
282            ChartPreset::Dark => {
283                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
284                    200 >> 3,
285                    200 >> 2,
286                    200 >> 3,
287                ));
288                builder = builder.line_color(color).line_width(1);
289            }
290        }
291        builder
292    }
293}
294
295/// Fluent builder for bar charts
296#[cfg(feature = "bar")]
297pub struct FluentBarChartBuilder<C: PixelColor> {
298    data: Option<StaticDataSeries<Point2D, 256>>,
299    color: Option<C>,
300    title: Option<String<64>>,
301    preset: Option<ChartPreset>,
302    bar_width: Option<u32>,
303}
304
305#[cfg(feature = "bar")]
306impl<C: PixelColor + 'static> FluentBarChartBuilder<C>
307where
308    C: From<embedded_graphics::pixelcolor::Rgb565>,
309{
310    fn new() -> Self {
311        Self {
312            data: None,
313            color: None,
314            title: None,
315            preset: None,
316            bar_width: None,
317        }
318    }
319
320    /// Set data from an array of tuples
321    pub fn data_from_tuples(mut self, tuples: &[(f32, f32)]) -> Self {
322        let series =
323            StaticDataSeries::from_tuples(tuples).unwrap_or_else(|_| StaticDataSeries::new());
324        self.data = Some(series);
325        self
326    }
327
328    /// Set the bar color
329    pub fn color(mut self, color: C) -> Self {
330        self.color = Some(color);
331        self
332    }
333
334    /// Set the chart title
335    pub fn title(mut self, title: &str) -> Self {
336        if let Ok(title_string) = String::try_from(title) {
337            self.title = Some(title_string);
338        }
339        self
340    }
341
342    /// Apply a preset style
343    pub fn preset(mut self, preset: ChartPreset) -> Self {
344        self.preset = Some(preset);
345        self
346    }
347
348    /// Set bar width
349    pub fn bar_width(mut self, width: u32) -> Self {
350        self.bar_width = Some(width);
351        self
352    }
353
354    /// Build the bar chart
355    pub fn build(self) -> ChartResult<crate::chart::BarChart<C>> {
356        let mut builder = crate::chart::BarChart::builder();
357
358        // Apply preset styling first
359        if let Some(preset) = self.preset {
360            builder = self.apply_preset_to_bar_builder(builder, preset);
361        }
362
363        // Apply specific customizations
364        if let Some(color) = self.color {
365            builder = builder.colors(&[color]);
366        }
367
368        if let Some(width) = self.bar_width {
369            builder = builder.bar_width(crate::chart::bar::BarWidth::Fixed(width));
370        }
371
372        builder.build()
373    }
374
375    fn apply_preset_to_bar_builder(
376        &self,
377        mut builder: crate::chart::BarChartBuilder<C>,
378        preset: ChartPreset,
379    ) -> crate::chart::BarChartBuilder<C> {
380        match preset {
381            ChartPreset::Professional => {
382                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
383                    59 >> 3,
384                    130 >> 2,
385                    246 >> 3,
386                ));
387                builder = builder.colors(&[color]);
388            }
389            ChartPreset::Embedded => {
390                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(0, 31, 0));
391                builder = builder.colors(&[color]);
392            }
393            ChartPreset::Vibrant => {
394                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
395                    245 >> 3,
396                    101 >> 2,
397                    101 >> 3,
398                ));
399                builder = builder.colors(&[color]);
400            }
401            ChartPreset::Pastel => {
402                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
403                    167 >> 3,
404                    243 >> 2,
405                    208 >> 3,
406                ));
407                builder = builder.colors(&[color]);
408            }
409            ChartPreset::Dark => {
410                let color = C::from(embedded_graphics::pixelcolor::Rgb565::new(
411                    150 >> 3,
412                    150 >> 2,
413                    150 >> 3,
414                ));
415                builder = builder.colors(&[color]);
416            }
417        }
418        builder
419    }
420}
421
422// Similar implementations for other chart types would follow...
423
424/// Quick creation functions for common chart types
425pub mod quick {
426    #[cfg(any(feature = "line", feature = "bar"))]
427    use super::*;
428
429    /// Create a simple line chart from data tuples
430    #[cfg(feature = "line")]
431    pub fn line_chart<C>(data: &[(f32, f32)]) -> ChartResult<crate::chart::LineChart<C>>
432    where
433        C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565> + 'static,
434    {
435        Chart::line().data_from_tuples(data).build()
436    }
437
438    /// Create a professional line chart from data tuples
439    #[cfg(feature = "line")]
440    pub fn professional_line_chart<C>(
441        data: &[(f32, f32)],
442    ) -> ChartResult<crate::chart::LineChart<C>>
443    where
444        C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565> + 'static,
445    {
446        Chart::line()
447            .data_from_tuples(data)
448            .preset(ChartPreset::Professional)
449            .with_markers()
450            .build()
451    }
452
453    /// Create a simple bar chart from data tuples
454    #[cfg(feature = "bar")]
455    pub fn bar_chart<C>(data: &[(f32, f32)]) -> ChartResult<crate::chart::BarChart<C>>
456    where
457        C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565> + 'static,
458    {
459        Chart::bar().data_from_tuples(data).build()
460    }
461
462    /// Create an embedded-optimized chart (high contrast, minimal styling)
463    #[cfg(feature = "line")]
464    pub fn embedded_line_chart<C>(data: &[(f32, f32)]) -> ChartResult<crate::chart::LineChart<C>>
465    where
466        C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565> + 'static,
467    {
468        Chart::line()
469            .data_from_tuples(data)
470            .preset(ChartPreset::Embedded)
471            .build()
472    }
473}
474
475#[cfg(test)]
476mod tests {
477    #[cfg(feature = "line")]
478    use super::*;
479    #[cfg(feature = "line")]
480    use embedded_graphics::pixelcolor::Rgb565;
481
482    #[test]
483    #[cfg(feature = "line")]
484    fn test_fluent_line_chart() {
485        let data = [(0.0, 10.0), (1.0, 20.0), (2.0, 15.0)];
486        let chart = Chart::line::<Rgb565>()
487            .data_from_tuples(&data)
488            .color(Rgb565::BLUE)
489            .title("Test Chart")
490            .with_markers()
491            .build();
492
493        assert!(chart.is_ok());
494    }
495
496    #[test]
497    #[cfg(feature = "line")]
498    fn test_quick_line_chart() {
499        let data = [(0.0, 10.0), (1.0, 20.0), (2.0, 15.0)];
500        let chart = quick::line_chart::<Rgb565>(&data);
501        assert!(chart.is_ok());
502    }
503
504    #[test]
505    #[cfg(feature = "line")]
506    fn test_professional_preset() {
507        let data = [(0.0, 10.0), (1.0, 20.0)];
508        let chart = Chart::line::<Rgb565>()
509            .data_from_tuples(&data)
510            .preset(ChartPreset::Professional)
511            .build();
512
513        assert!(chart.is_ok());
514    }
515
516    #[test]
517    #[cfg(feature = "line")]
518    fn test_multi_series() {
519        let chart = Chart::line::<Rgb565>()
520            .series("Series 1", &[(0.0, 10.0), (1.0, 20.0)])
521            .series("Series 2", &[(0.0, 15.0), (1.0, 25.0)])
522            .preset(ChartPreset::Vibrant)
523            .build();
524
525        assert!(chart.is_ok());
526    }
527}