embedded_charts/
lib.rs

1//! # Embedded Charts
2//!
3//! A production-ready, no_std graph framework for embedded systems using embedded-graphics.
4//!
5//! This library provides comprehensive chart types (line, bar, pie, scatter, gauge), axes, grids,
6//! legends, real-time data streaming capabilities, and customizable styling while maintaining
7//! memory efficiency and performance suitable for resource-constrained environments.
8//!
9//! ## Features
10//!
11//! - **Memory Efficient**: Static allocation with compile-time bounds, no heap usage
12//! - **Performance Optimized**: Designed for real-time rendering on embedded systems
13//! - **Flexible**: Plugin-like architecture for custom chart types
14//! - **Easy to Use**: Fluent builder API with sensible defaults
15//! - **std/no_std Compatible**: Full compatibility with both desktop and embedded environments
16//! - **Rich Chart Types**: Line, bar, pie, gauge, and scatter charts with professional styling
17//! - **Real-time Animation**: Streaming data with smooth transitions and configurable easing
18//! - **Professional Styling**: Built-in themes, color palettes, and typography support
19//!
20//! ## Chart Types
21//!
22//! ### Line Charts
23//! Multi-series line charts with markers, area filling, and smooth curves:
24//! ```rust,no_run
25//! # #[cfg(feature = "line")]
26//! # {
27//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
28//! use embedded_charts::prelude::*;
29//! use embedded_graphics::pixelcolor::Rgb565;
30//!
31//! let chart = LineChart::builder()
32//!     .line_color(Rgb565::BLUE)
33//!     .line_width(2)
34//!     .with_markers(MarkerStyle {
35//!         shape: MarkerShape::Circle,
36//!         size: 6,
37//!         color: Rgb565::RED,
38//!         visible: true,
39//!     })
40//!     .build()?;
41//! # Ok(())
42//! # }
43//! # }
44//! ```
45//!
46//! ### Bar Charts
47//! Vertical and horizontal bar charts with stacking support:
48//! ```rust,no_run
49//! # #[cfg(feature = "bar")]
50//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
51//! use embedded_charts::prelude::*;
52//! use embedded_graphics::pixelcolor::Rgb565;
53//!
54//! let chart = BarChart::builder()
55//!     .orientation(BarOrientation::Vertical)
56//!     .bar_width(BarWidth::Fixed(20))
57//!     .colors(&[Rgb565::GREEN])
58//!     .build()?;
59//! Ok(())
60//! # }
61//! ```
62//!
63//! ### Pie Charts
64//! Full circle pie charts with professional styling:
65//! ```rust,no_run
66//! # #[cfg(feature = "pie")]
67//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
68//! use embedded_charts::prelude::*;
69//! use embedded_graphics::pixelcolor::Rgb565;
70//!
71//! let chart = PieChart::builder()
72//!     .radius(80)
73//!     .colors(&[Rgb565::BLUE, Rgb565::RED, Rgb565::GREEN])
74//!     .with_title("Market Share")
75//!     .build()?;
76//! Ok(())
77//! # }
78//! ```
79//!
80//! ### Donut Charts
81//! Pie charts with hollow centers for improved information density:
82//! ```rust,no_run
83//! # #[cfg(feature = "pie")]
84//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
85//! use embedded_charts::prelude::*;
86//! use embedded_graphics::pixelcolor::Rgb565;
87//!
88//! // Balanced donut chart (50% inner radius)
89//! let chart = PieChart::builder()
90//!     .radius(80)
91//!     .donut(40) // Inner radius of 40 pixels
92//!     .colors(&[Rgb565::BLUE, Rgb565::RED, Rgb565::GREEN])
93//!     .with_title("Storage Usage")
94//!     .build()?;
95//! Ok(())
96//! # }
97//! ```
98//!
99//! #### Donut Chart Best Practices for Embedded Systems
100//!
101//! **Optimal Inner Radius Ratios:**
102//! - **Thin donut (20-30% inner)**: Emphasizes data segments, good for detailed analysis
103//! - **Balanced donut (40-60% inner)**: Best overall readability and visual balance
104//! - **Thick donut (70-80% inner)**: Maximizes center space for additional content
105//!
106//! **Memory Considerations:**
107//! - Donut charts use the same memory footprint as regular pie charts
108//! - Center area can display totals, units, or status without additional memory cost
109//! - Smaller outer radius improves performance on resource-constrained systems
110//!
111//! **Display Size Guidelines:**
112//! - Small displays (≤128px): Use 50% inner radius with 60px outer radius
113//! - Medium displays (240px): Use 40-60% inner radius with 80px outer radius  
114//! - Large displays (≥480px): Use any ratio with 100px+ outer radius for clarity
115//!
116//! ### Gauge Charts
117//! Semicircle gauges with threshold zones and custom indicators:
118//! ```rust,no_run
119//! # #[cfg(feature = "gauge")]
120//! # fn test() -> Result<(), embedded_charts::error::ChartError> {
121//! use embedded_charts::prelude::*;
122//! use embedded_graphics::pixelcolor::Rgb565;
123//!
124//! let chart = GaugeChart::builder()
125//!     .gauge_type(GaugeType::Semicircle)
126//!     .value_range(0.0, 100.0)
127//!     .add_threshold_zone(70.0, 100.0, Rgb565::RED)
128//!     .build()?;
129//! Ok(())
130//! # }
131//! ```
132//!
133//! ## Data Management
134//!
135//! ### Static Data Series
136//! Fixed-capacity data storage for predictable memory usage:
137//! ```rust
138//! use embedded_charts::prelude::*;
139//!
140//! // Create a series with capacity for 256 points
141//! let mut series: StaticDataSeries<Point2D, 256> = StaticDataSeries::new();
142//! series.push(Point2D::new(0.0, 10.0))?;
143//! series.push(Point2D::new(1.0, 20.0))?;
144//!
145//! // Create from tuples using the macro
146//! let data = data_points![(0.0, 10.0), (1.0, 20.0), (2.0, 15.0)];
147//! # Ok::<(), embedded_charts::error::DataError>(())
148//! ```
149//!
150//! ### Multi-Series Data
151//! Container for multiple data series with automatic color assignment:
152//! ```rust
153//! use embedded_charts::prelude::*;
154//!
155//! // Container for 8 series, 256 points each
156//! let mut multi_series: MultiSeries<Point2D, 8, 256> = MultiSeries::new();
157//!
158//! let temp_data = data_points![(0.0, 22.5), (1.0, 23.1), (2.0, 24.2)];
159//! let humidity_data = data_points![(0.0, 65.0), (1.0, 68.0), (2.0, 72.0)];
160//!
161//! multi_series.add_series(temp_data)?;
162//! multi_series.add_series(humidity_data)?;
163//! # Ok::<(), embedded_charts::error::DataError>(())
164//! ```
165//!
166//! ## Professional Styling
167//!
168//! ### Themes and Color Palettes
169//! Built-in themes optimized for different display types:
170//! ```rust,no_run
171//! # #[cfg(feature = "color-support")]
172//! # {
173//! use embedded_charts::prelude::*;
174//!
175//! // Professional color palettes
176//! let colors = quick::professional_colors();
177//! let nature_colors = quick::nature_colors();
178//! let ocean_colors = quick::ocean_colors();
179//!
180//! // Complete themes
181//! let light_theme = quick::light_theme();
182//! let dark_theme = quick::dark_theme();
183//! let cyberpunk_theme = quick::cyberpunk_theme();
184//! # }
185//! ```
186//!
187//! ### Chart Configuration
188//! Fluent configuration with the `chart_config!` macro:
189//! ```rust
190//! use embedded_charts::prelude::*;
191//! use embedded_graphics::pixelcolor::Rgb565;
192//!
193//! let config = chart_config! {
194//!     title: "Temperature Monitor",
195//!     background: Rgb565::WHITE,
196//!     margins: constants::DEFAULT_MARGINS,
197//!     grid: true,
198//! };
199//! ```
200//!
201//! ## Real-time Animation
202//!
203//! ### Streaming Data (requires "animations" feature)
204//! ```rust,no_run
205//! # #[cfg(all(feature = "animations", feature = "line"))]
206//! # {
207//! use embedded_charts::prelude::*;
208//! use embedded_graphics::{prelude::*, pixelcolor::Rgb565};
209//!
210//! // Sliding window for real-time data
211//! let mut streaming_data: SlidingWindowSeries<Point2D, 100> =
212//!     SlidingWindowSeries::new();
213//!
214//! // Add data points (automatically removes old ones)
215//! let timestamp = 1.0;
216//! let value = 25.0;
217//! streaming_data.push(Point2D::new(timestamp, value));
218//!
219//! // Create a chart for rendering
220//! let chart: LineChart<Rgb565> = LineChart::builder().build()?;
221//! let config: ChartConfig<Rgb565> = ChartConfig::default();
222//! let viewport = Rectangle::new(Point::zero(), Size::new(320, 240));
223//! // chart.draw(&streaming_data, &config, viewport, &mut display)?;
224//! # }
225//! # Ok::<(), embedded_charts::error::ChartError>(())
226//! ```
227//!
228//! ## System Optimization
229//!
230//! ### Feature Configuration
231//! Choose the appropriate feature set for your target system's capabilities:
232//!
233//! ```toml
234//! # Minimal configuration - Integer math only
235//! [dependencies]
236//! embedded-charts = {
237//!     version = "0.1.0",
238//!     default-features = false,
239//!     features = ["integer-math"]
240//! }
241//!
242//! # Balanced configuration - Fixed-point math with color support
243//! [dependencies]
244//! embedded-charts = {
245//!     version = "0.1.0",
246//!     default-features = false,
247//!     features = ["fixed-point", "color-support"]
248//! }
249//!
250//! # Full-featured configuration - All features enabled
251//! [dependencies]
252//! embedded-charts = {
253//!     version = "0.1.0",
254//!     default-features = false,
255//!     features = ["floating-point", "animations", "color-support"]
256//! }
257//! ```
258//!
259//! ### no_std Usage
260//! Complete example for embedded systems:
261//! ```rust,ignore
262//! #![no_std]
263//!
264//! use embedded_charts::prelude::*;
265//! use embedded_graphics::{pixelcolor::Rgb565, prelude::*};
266//!
267//! fn render_sensor_chart() -> Result<(), embedded_charts::error::ChartError> {
268//!     // Create data series with static allocation
269//!     let mut sensor_data: StaticDataSeries<Point2D, 64> = StaticDataSeries::new();
270//!     let _ = sensor_data.push(Point2D::new(0.0, 22.5));
271//!     let _ = sensor_data.push(Point2D::new(1.0, 23.1));
272//!
273//!     // Create minimal chart for small displays
274//!     let chart = LineChart::builder()
275//!         .line_color(Rgb565::BLUE)
276//!         .build()?;
277//!
278//!     // Render to embedded display
279//!     let viewport = Rectangle::new(Point::zero(), Size::new(128, 64));
280//!     // chart.draw(&sensor_data, chart.config(), viewport, &mut display)?;
281//!     Ok(())
282//! }
283//!
284//! fn main() {
285//!     let _ = render_sensor_chart();
286//! }
287//! ```
288//!
289//! ## Complete Example
290//!
291//! Professional multi-series chart:
292//! ```rust,ignore
293//! use embedded_charts::prelude::*;
294//! use embedded_graphics::{pixelcolor::Rgb565, prelude::*};
295//!
296//! // Create sample data
297//! let temp_data = data_points![(0.0, 22.5), (1.0, 23.1), (2.0, 24.2), (3.0, 23.8)];
298//! let humidity_data = data_points![(0.0, 65.0), (1.0, 68.0), (2.0, 72.0), (3.0, 70.0)];
299//!
300//! // Create multi-series container
301//! let mut multi_series: MultiSeries<Point2D, 8, 256> = MultiSeries::new();
302//! multi_series.add_series(temp_data)?;
303//! multi_series.add_series(humidity_data)?;
304//!
305//! // Create a simple line chart
306//! let chart = LineChart::builder()
307//!     .line_color(Rgb565::BLUE)
308//!     .build()?;
309//!
310//! // Configure the chart
311//! let config: ChartConfig<Rgb565> = ChartConfig::default();
312//!
313//! // Render to display
314//! let viewport = Rectangle::new(Point::zero(), Size::new(320, 240));
315//! // chart.draw(&multi_series, &config, viewport, &mut display)?;
316//! # Ok::<(), embedded_charts::error::ChartError>(())
317//! ```
318//!
319//! ## Module Organization
320//!
321//! - [`chart`] - Chart implementations (line, bar, pie, gauge, scatter)
322//! - [`data`] - Data series and point management
323//! - [`fluent`] - Fluent API for easy chart creation
324//! - [`style`] - Styling, themes, and color palettes
325//! - [`axes`] - Axis configuration and rendering
326//! - [`grid`] - Grid system for chart backgrounds
327//! - [`legend`] - Legend positioning and styling
328//! - [`animation`] - Real-time animations and transitions (feature-gated)
329//! - [`render`] - Low-level rendering primitives
330//! - [`layout`] - Chart layout and positioning
331//! - [`memory`] - Memory management utilities
332//! - [`time`] - Time abstraction for animations
333//! - [`math`] - Mathematical operations abstraction
334//! - [`error`] - Error types and handling
335//! - [`prelude`] - Convenient re-exports for common usage
336//!
337//! For complete API documentation, see the [API Documentation](API_DOCUMENTATION.md).
338
339#![cfg_attr(feature = "no_std", no_std)]
340#![deny(missing_docs)]
341#![deny(unsafe_code)]
342
343// Conditional std imports
344#[cfg(feature = "std")]
345extern crate std;
346
347#[cfg(all(feature = "no_std", not(feature = "std")))]
348extern crate alloc;
349
350// Math abstraction layer - always available
351pub mod math;
352
353// Core modules
354pub mod chart;
355pub mod data;
356pub mod fluent;
357pub mod layout;
358pub mod render;
359pub mod style;
360
361// Grid system
362pub mod grid;
363
364// Optional modules based on features
365#[cfg(feature = "animations")]
366pub mod animation;
367
368// Time abstraction layer
369pub mod time;
370
371pub mod axes;
372
373pub mod legend;
374
375// Memory management utilities
376pub mod memory;
377
378// Heapless utilities for enhanced no_std support
379pub mod heapless_utils;
380
381// Dashboard layout system
382pub mod dashboard;
383
384// Convenience re-exports
385pub mod prelude;
386
387// Error types
388pub mod error;
389
390// Re-export commonly used types
391pub use embedded_graphics;
392pub use heapless;
393
394// Re-export math types for convenience
395pub use math::{Math, Number};
396
397/// Current version of the library
398pub const VERSION: &str = env!("CARGO_PKG_VERSION");
399
400/// Library configuration and feature detection
401pub mod config {
402    /// Check if std is available
403    pub const fn has_std() -> bool {
404        cfg!(feature = "std")
405    }
406
407    /// Check if floating-point math is available
408    pub const fn has_floating_point() -> bool {
409        cfg!(feature = "floating-point")
410    }
411
412    /// Check if fixed-point math is available
413    pub const fn has_fixed_point() -> bool {
414        cfg!(feature = "fixed-point")
415    }
416
417    /// Check if integer-only math is being used
418    pub const fn has_integer_math() -> bool {
419        cfg!(feature = "integer-math")
420    }
421
422    /// Check if animations are available
423    pub const fn has_animations() -> bool {
424        cfg!(feature = "animations")
425    }
426
427    /// Get the math backend name
428    pub const fn math_backend() -> &'static str {
429        #[cfg(feature = "floating-point")]
430        return "floating-point";
431
432        #[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
433        return "libm";
434
435        #[cfg(all(
436            feature = "fixed-point",
437            not(any(feature = "floating-point", feature = "libm-math"))
438        ))]
439        return "fixed-point";
440
441        #[cfg(all(
442            feature = "cordic-math",
443            not(any(
444                feature = "floating-point",
445                feature = "libm-math",
446                feature = "fixed-point"
447            ))
448        ))]
449        return "cordic";
450
451        #[cfg(all(
452            feature = "integer-math",
453            not(any(
454                feature = "floating-point",
455                feature = "libm-math",
456                feature = "fixed-point",
457                feature = "cordic-math"
458            ))
459        ))]
460        return "integer";
461
462        #[cfg(not(any(
463            feature = "floating-point",
464            feature = "libm-math",
465            feature = "fixed-point",
466            feature = "cordic-math",
467            feature = "integer-math"
468        )))]
469        return "default-floating-point";
470    }
471
472    /// Get the system configuration category based on enabled features
473    pub const fn system_category() -> &'static str {
474        #[cfg(feature = "integer-math")]
475        {
476            "minimal"
477        }
478        #[cfg(all(feature = "fixed-point", not(feature = "integer-math")))]
479        {
480            "balanced"
481        }
482        #[cfg(all(
483            feature = "floating-point",
484            not(any(feature = "integer-math", feature = "fixed-point"))
485        ))]
486        {
487            "full-featured"
488        }
489        #[cfg(not(any(
490            feature = "integer-math",
491            feature = "fixed-point",
492            feature = "floating-point"
493        )))]
494        {
495            "default"
496        }
497    }
498}