embedded_charts/chart/traits.rs
1//! Core traits for chart implementations.
2
3use crate::data::DataSeries;
4use crate::error::ChartResult;
5use embedded_graphics::{prelude::*, primitives::Rectangle};
6
7/// Main trait for all chart types
8pub trait Chart<C: PixelColor> {
9 /// The type of data this chart can render
10 type Data: DataSeries;
11 /// Configuration type for this chart
12 type Config;
13
14 /// Draw the chart to the target display
15 ///
16 /// # Arguments
17 /// * `data` - The data to render
18 /// * `config` - Chart configuration
19 /// * `viewport` - The area to draw the chart in
20 /// * `target` - The display target to draw to
21 fn draw<D>(
22 &self,
23 data: &Self::Data,
24 config: &Self::Config,
25 viewport: Rectangle,
26 target: &mut D,
27 ) -> ChartResult<()>
28 where
29 D: DrawTarget<Color = C>;
30
31 /// Get the data bounds for this chart
32 fn data_bounds(&self, _data: &Self::Data) -> ChartResult<()> {
33 // Default implementation - concrete charts should override this
34 // if they need specific bounds calculation
35 Ok(())
36 }
37}
38
39/// Trait for charts that support real-time data streaming
40#[cfg(feature = "animations")]
41pub trait StreamingChart<C: PixelColor>: Chart<C> {
42 /// The type of individual data points
43 type DataPoint: Copy + Clone;
44
45 /// Get the streaming animator for this chart
46 fn streaming_animator(&mut self) -> &mut crate::animation::StreamingAnimator<Self::DataPoint>;
47
48 /// Push a new data point to the chart
49 ///
50 /// # Arguments
51 /// * `point` - The new data point to add
52 fn push_data(&mut self, point: Self::DataPoint) -> ChartResult<()> {
53 self.streaming_animator().push_data(point);
54 Ok(())
55 }
56
57 /// Draw the chart with streaming data and interpolation
58 ///
59 /// # Arguments
60 /// * `config` - Chart configuration
61 /// * `viewport` - The area to draw the chart in
62 /// * `target` - The display target to draw to
63 /// * `interpolation_progress` - Progress for smooth interpolation (0-100)
64 fn draw_streaming<D>(
65 &self,
66 config: &Self::Config,
67 viewport: embedded_graphics::primitives::Rectangle,
68 target: &mut D,
69 interpolation_progress: crate::animation::Progress,
70 ) -> ChartResult<()>
71 where
72 D: embedded_graphics::draw_target::DrawTarget<Color = C>;
73
74 /// Enable or disable smooth interpolation for streaming data
75 ///
76 /// # Arguments
77 /// * `enabled` - Whether to enable smooth interpolation
78 fn set_smooth_interpolation(&mut self, enabled: bool) {
79 self.streaming_animator().set_smooth_interpolation(enabled);
80 }
81
82 /// Check if smooth interpolation is enabled
83 fn is_smooth_interpolation_enabled(&self) -> bool;
84}
85
86/// Trait for charts that can be animated using the new progress-based system
87#[cfg(feature = "animations")]
88pub trait AnimatedChart<C: PixelColor>: Chart<C> {
89 /// The type of data that can be animated
90 type AnimatedData: crate::animation::Interpolatable;
91
92 /// Draw the chart with animation at the specified progress
93 ///
94 /// # Arguments
95 /// * `data` - The data to render
96 /// * `config` - Chart configuration
97 /// * `viewport` - The area to draw the chart in
98 /// * `target` - The display target to draw to
99 /// * `progress` - Animation progress (0-100)
100 fn draw_animated<D>(
101 &self,
102 data: &Self::Data,
103 config: &Self::Config,
104 viewport: embedded_graphics::primitives::Rectangle,
105 target: &mut D,
106 progress: crate::animation::Progress,
107 ) -> ChartResult<()>
108 where
109 D: embedded_graphics::draw_target::DrawTarget<Color = C>;
110
111 /// Set up a transition animation between two data states
112 ///
113 /// # Arguments
114 /// * `from_data` - Starting data state
115 /// * `to_data` - Target data state
116 /// * `easing` - Easing function to use
117 ///
118 /// # Returns
119 /// A ChartAnimator that can be used to interpolate between states
120 fn create_transition_animator(
121 &self,
122 from_data: Self::AnimatedData,
123 to_data: Self::AnimatedData,
124 easing: crate::animation::EasingFunction,
125 ) -> crate::animation::ChartAnimator<Self::AnimatedData>;
126
127 /// Extract animatable data from the chart's data
128 ///
129 /// # Arguments
130 /// * `data` - The chart data to extract from
131 ///
132 /// # Returns
133 /// The animatable representation of the data
134 fn extract_animated_data(&self, data: &Self::Data) -> ChartResult<Self::AnimatedData>;
135}
136
137/// Trait for customizable chart styling
138pub trait StylableChart<C: PixelColor> {
139 /// Style configuration type
140 type Style;
141
142 /// Apply a style to the chart
143 ///
144 /// # Arguments
145 /// * `style` - The style configuration to apply
146 fn apply_style(&mut self, style: Self::Style);
147
148 /// Get the current style
149 fn style(&self) -> &Self::Style;
150}
151
152/// Trait for charts with configurable axes
153pub trait AxisChart<C: PixelColor>: Chart<C> {
154 /// X-axis type
155 type XAxis;
156 /// Y-axis type
157 type YAxis;
158
159 /// Set the X-axis configuration
160 ///
161 /// # Arguments
162 /// * `axis` - X-axis configuration
163 fn set_x_axis(&mut self, axis: Self::XAxis);
164
165 /// Set the Y-axis configuration
166 ///
167 /// # Arguments
168 /// * `axis` - Y-axis configuration
169 fn set_y_axis(&mut self, axis: Self::YAxis);
170
171 /// Get the X-axis configuration
172 fn x_axis(&self) -> ChartResult<&Self::XAxis>;
173
174 /// Get the Y-axis configuration
175 fn y_axis(&self) -> ChartResult<&Self::YAxis>;
176}
177
178/// Trait for charts that support legends
179pub trait LegendChart<C: PixelColor>: Chart<C> {
180 /// Legend configuration type
181 type Legend;
182
183 /// Set the legend configuration
184 ///
185 /// # Arguments
186 /// * `legend` - Legend configuration
187 fn set_legend(&mut self, legend: Option<Self::Legend>);
188
189 /// Get the legend configuration
190 fn legend(&self) -> Option<&Self::Legend>;
191
192 /// Calculate the space required for the legend
193 fn legend_size(&self) -> Size;
194}
195
196/// Builder trait for fluent chart construction
197pub trait ChartBuilder<C: PixelColor> {
198 /// The chart type this builder creates
199 type Chart: Chart<C>;
200 /// Error type for building operations
201 type Error;
202
203 /// Build the chart with current configuration
204 fn build(self) -> Result<Self::Chart, Self::Error>;
205}
206
207/// Trait for charts that can be rendered incrementally
208pub trait IncrementalChart<C: PixelColor>: Chart<C> {
209 /// Render only the changed portions of the chart
210 ///
211 /// # Arguments
212 /// * `data` - The data to render
213 /// * `config` - Chart configuration
214 /// * `viewport` - The area to draw the chart in
215 /// * `target` - The display target to draw to
216 /// * `dirty_region` - The region that needs to be redrawn
217 fn draw_incremental<D>(
218 &self,
219 data: &Self::Data,
220 config: &Self::Config,
221 viewport: Rectangle,
222 target: &mut D,
223 dirty_region: Rectangle,
224 ) -> ChartResult<()>
225 where
226 D: DrawTarget<Color = C>;
227
228 /// Mark a region as dirty (needing redraw)
229 ///
230 /// # Arguments
231 /// * `region` - The region to mark as dirty
232 fn mark_dirty(&mut self, region: Rectangle);
233
234 /// Get all dirty regions
235 fn dirty_regions(&self) -> &[Rectangle];
236
237 /// Clear all dirty regions
238 fn clear_dirty(&mut self);
239}
240
241/// Trait for charts that support interaction
242pub trait InteractiveChart<C: PixelColor>: Chart<C> {
243 /// Event type for interactions
244 type Event;
245 /// Response type for interactions
246 type Response;
247
248 /// Handle an interaction event
249 ///
250 /// # Arguments
251 /// * `event` - The interaction event
252 /// * `viewport` - The chart viewport
253 fn handle_event(
254 &mut self,
255 event: Self::Event,
256 viewport: Rectangle,
257 ) -> ChartResult<Self::Response>;
258
259 /// Check if a point is within an interactive area
260 ///
261 /// # Arguments
262 /// * `point` - The point to check
263 /// * `viewport` - The chart viewport
264 fn hit_test(&self, point: Point, viewport: Rectangle) -> Option<Self::Response>;
265}
266
267/// Trait for chart renderers that support animation frame rendering
268#[cfg(feature = "animations")]
269pub trait AnimationRenderer<C: PixelColor> {
270 /// Check if the renderer needs frame updates
271 fn needs_frame_update(&self) -> bool;
272
273 /// Get the current frame rate (FPS)
274 fn frame_rate(&self) -> u32;
275
276 /// Set the target frame rate
277 fn set_frame_rate(&mut self, fps: u32);
278}
279
280/// Common chart configuration
281#[derive(Debug, Clone)]
282pub struct ChartConfig<C: PixelColor> {
283 /// Chart title
284 pub title: Option<heapless::String<64>>,
285 /// Background color
286 pub background_color: Option<C>,
287 /// Chart margins
288 pub margins: Margins,
289 /// Whether to show grid lines
290 pub show_grid: bool,
291 /// Grid color
292 pub grid_color: Option<C>,
293}
294
295/// Chart margins configuration
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub struct Margins {
298 /// Top margin in pixels
299 pub top: u32,
300 /// Right margin in pixels
301 pub right: u32,
302 /// Bottom margin in pixels
303 pub bottom: u32,
304 /// Left margin in pixels
305 pub left: u32,
306}
307
308impl Margins {
309 /// Create new margins with the same value for all sides
310 pub const fn all(margin: u32) -> Self {
311 Self {
312 top: margin,
313 right: margin,
314 bottom: margin,
315 left: margin,
316 }
317 }
318
319 /// Create new margins with different horizontal and vertical values
320 pub const fn symmetric(horizontal: u32, vertical: u32) -> Self {
321 Self {
322 top: vertical,
323 right: horizontal,
324 bottom: vertical,
325 left: horizontal,
326 }
327 }
328
329 /// Create new margins with individual values
330 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
331 Self {
332 top,
333 right,
334 bottom,
335 left,
336 }
337 }
338
339 /// Get the total horizontal margin (left + right)
340 pub const fn horizontal(&self) -> u32 {
341 self.left + self.right
342 }
343
344 /// Get the total vertical margin (top + bottom)
345 pub const fn vertical(&self) -> u32 {
346 self.top + self.bottom
347 }
348
349 /// Apply margins to a rectangle, returning the inner area
350 pub fn apply_to(&self, rect: Rectangle) -> Rectangle {
351 let top_left = Point::new(
352 rect.top_left.x + self.left as i32,
353 rect.top_left.y + self.top as i32,
354 );
355 let size = Size::new(
356 rect.size.width.saturating_sub(self.horizontal()),
357 rect.size.height.saturating_sub(self.vertical()),
358 );
359 Rectangle::new(top_left, size)
360 }
361}
362
363impl Default for Margins {
364 fn default() -> Self {
365 Self::all(10)
366 }
367}
368
369impl<C: PixelColor> Default for ChartConfig<C> {
370 fn default() -> Self {
371 Self {
372 title: None,
373 background_color: None,
374 margins: Margins::default(),
375 show_grid: false,
376 grid_color: None,
377 }
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_margins_creation() {
387 let margins = Margins::all(5);
388 assert_eq!(margins.top, 5);
389 assert_eq!(margins.right, 5);
390 assert_eq!(margins.bottom, 5);
391 assert_eq!(margins.left, 5);
392 }
393
394 #[test]
395 fn test_margins_symmetric() {
396 let margins = Margins::symmetric(10, 20);
397 assert_eq!(margins.top, 20);
398 assert_eq!(margins.right, 10);
399 assert_eq!(margins.bottom, 20);
400 assert_eq!(margins.left, 10);
401 }
402
403 #[test]
404 fn test_margins_totals() {
405 let margins = Margins::new(5, 10, 15, 20);
406 assert_eq!(margins.horizontal(), 30);
407 assert_eq!(margins.vertical(), 20);
408 }
409
410 #[test]
411 fn test_margins_apply_to() {
412 let margins = Margins::all(10);
413 let rect = Rectangle::new(Point::new(0, 0), Size::new(100, 80));
414 let inner = margins.apply_to(rect);
415
416 assert_eq!(inner.top_left, Point::new(10, 10));
417 assert_eq!(inner.size, Size::new(80, 60));
418 }
419}