1use crate::chart::line::{LineChart, LineChartBuilder, LineChartStyle, MarkerStyle};
8use crate::chart::traits::{Chart, ChartBuilder, ChartConfig};
9use crate::data::{DataPoint, DataSeries, Point2D};
10use crate::error::{ChartError, ChartResult};
11use crate::math::interpolation::{CurveInterpolator, InterpolationConfig, InterpolationType};
12use embedded_graphics::{draw_target::DrawTarget, prelude::*};
13use heapless::Vec;
14
15#[derive(Debug)]
60pub struct CurveChart<C: PixelColor> {
61 base_chart: LineChart<C>,
63 interpolation_config: InterpolationConfig,
65}
66
67impl<C: PixelColor + 'static> CurveChart<C>
68where
69 C: From<embedded_graphics::pixelcolor::Rgb565>,
70{
71 pub fn new() -> Self {
79 Self {
80 base_chart: LineChart::new(),
81 interpolation_config: InterpolationConfig::default(),
82 }
83 }
84
85 pub fn builder() -> CurveChartBuilder<C> {
87 CurveChartBuilder::new()
88 }
89
90 pub fn set_interpolation_config(&mut self, config: InterpolationConfig) {
95 self.interpolation_config = config;
96 }
97
98 pub fn interpolation_config(&self) -> &InterpolationConfig {
100 &self.interpolation_config
101 }
102
103 pub fn set_style(&mut self, style: LineChartStyle<C>) {
105 self.base_chart.set_style(style);
106 }
107
108 pub fn style(&self) -> &LineChartStyle<C> {
110 self.base_chart.style()
111 }
112
113 pub fn set_config(&mut self, config: ChartConfig<C>) {
115 self.base_chart.set_config(config);
116 }
117
118 pub fn config(&self) -> &ChartConfig<C> {
120 self.base_chart.config()
121 }
122
123 pub fn set_grid(&mut self, grid: Option<crate::grid::GridSystem<C>>) {
125 self.base_chart.set_grid(grid);
126 }
127
128 pub fn grid(&self) -> Option<&crate::grid::GridSystem<C>> {
130 self.base_chart.grid()
131 }
132
133 pub fn base_chart(&self) -> &LineChart<C> {
135 &self.base_chart
136 }
137
138 pub fn base_chart_mut(&mut self) -> &mut LineChart<C> {
140 &mut self.base_chart
141 }
142
143 fn interpolate_data(
145 &self,
146 data: &crate::data::series::StaticDataSeries<Point2D, 256>,
147 ) -> ChartResult<Vec<Point2D, 512>> {
148 let mut points = Vec::<Point2D, 256>::new();
150 for point in data.iter() {
151 points.push(point).map_err(|_| ChartError::MemoryFull)?;
152 }
153
154 CurveInterpolator::interpolate(&points, &self.interpolation_config)
156 }
157
158 fn transform_curve_point(
160 &self,
161 point: &Point2D,
162 data_bounds: &crate::data::DataBounds<f32, f32>,
163 viewport: embedded_graphics::primitives::Rectangle,
164 ) -> embedded_graphics::prelude::Point {
165 use crate::math::NumericConversion;
166
167 let data_x = point.x.to_number();
169 let data_y = point.y.to_number();
170
171 let min_x = data_bounds.min_x.to_number();
173 let max_x = data_bounds.max_x.to_number();
174 let min_y = data_bounds.min_y.to_number();
175 let max_y = data_bounds.max_y.to_number();
176
177 let draw_area = self.base_chart.config().margins.apply_to(viewport);
179
180 let norm_x = if f32::from_number(max_x) > f32::from_number(min_x) {
182 let range_x = f32::from_number(max_x - min_x);
183 let offset_x = f32::from_number(data_x - min_x);
184 (offset_x / range_x).to_number()
185 } else {
186 0.5f32.to_number()
187 };
188
189 let norm_y = if f32::from_number(max_y) > f32::from_number(min_y) {
190 let range_y = f32::from_number(max_y - min_y);
191 let offset_y = f32::from_number(data_y - min_y);
192 (offset_y / range_y).to_number()
193 } else {
194 0.5f32.to_number()
195 };
196
197 let norm_x_f32 = f32::from_number(norm_x);
199 let norm_y_f32 = f32::from_number(norm_y);
200
201 let screen_x =
202 draw_area.top_left.x + (norm_x_f32 * (draw_area.size.width as f32 - 1.0)) as i32;
203 let screen_y = draw_area.top_left.y + draw_area.size.height as i32
204 - 1
205 - (norm_y_f32 * (draw_area.size.height as f32 - 1.0)) as i32;
206
207 embedded_graphics::prelude::Point::new(screen_x, screen_y)
208 }
209}
210
211impl<C: PixelColor + 'static> Default for CurveChart<C>
212where
213 C: From<embedded_graphics::pixelcolor::Rgb565>,
214{
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220impl<C: PixelColor + 'static> Chart<C> for CurveChart<C>
221where
222 C: From<embedded_graphics::pixelcolor::Rgb565>,
223{
224 type Data = crate::data::series::StaticDataSeries<Point2D, 256>;
225 type Config = ChartConfig<C>;
226
227 fn draw<D>(
228 &self,
229 data: &Self::Data,
230 config: &Self::Config,
231 viewport: embedded_graphics::primitives::Rectangle,
232 target: &mut D,
233 ) -> ChartResult<()>
234 where
235 D: DrawTarget<Color = C>,
236 Self::Data: DataSeries,
237 <Self::Data as DataSeries>::Item: DataPoint,
238 <<Self::Data as DataSeries>::Item as DataPoint>::X: Into<f32> + Copy + PartialOrd,
239 <<Self::Data as DataSeries>::Item as DataPoint>::Y: Into<f32> + Copy + PartialOrd,
240 {
241 if data.is_empty() {
242 return Err(ChartError::InsufficientData);
243 }
244
245 if data.len() == 1 {
247 return self.base_chart.draw(data, config, viewport, target);
248 }
249
250 let interpolated_points = self.interpolate_data(data)?;
252
253 let mut curve_data = crate::data::series::StaticDataSeries::new();
255 for point in interpolated_points.iter() {
256 curve_data
257 .push(*point)
258 .map_err(|_| ChartError::MemoryFull)?;
259 }
260
261 let original_markers = self.base_chart.style().markers;
263
264 let mut temp_chart = LineChart::builder()
266 .line_color(self.base_chart.style().line_color)
267 .line_width(self.base_chart.style().line_width)
268 .fill_area(
269 self.base_chart
270 .style()
271 .fill_color
272 .unwrap_or(self.base_chart.style().line_color),
273 )
274 .smooth(false) .build()?;
276
277 if self.base_chart.style().fill_area {
278 if let Some(fill_color) = self.base_chart.style().fill_color {
279 temp_chart = LineChart::builder()
280 .line_color(self.base_chart.style().line_color)
281 .line_width(self.base_chart.style().line_width)
282 .fill_area(fill_color)
283 .smooth(false)
284 .build()?;
285 }
286 } else {
287 temp_chart = LineChart::builder()
288 .line_color(self.base_chart.style().line_color)
289 .line_width(self.base_chart.style().line_width)
290 .smooth(false)
291 .build()?;
292 }
293
294 temp_chart.draw(&curve_data, config, viewport, target)?;
296
297 if let Some(marker_style) = original_markers {
299 if marker_style.visible {
300 use embedded_graphics::primitives::{Circle, PrimitiveStyle};
301
302 let data_bounds = data.bounds()?;
303
304 for original_point in data.iter() {
305 let point_2d = crate::data::Point2D::new(original_point.x, original_point.y);
307 let screen_point =
309 self.transform_curve_point(&point_2d, &data_bounds, viewport);
310
311 let marker_primitive_style = PrimitiveStyle::with_fill(marker_style.color);
313 let radius = marker_style.size / 2;
314
315 Circle::new(
316 embedded_graphics::prelude::Point::new(
317 screen_point.x - radius as i32,
318 screen_point.y - radius as i32,
319 ),
320 marker_style.size,
321 )
322 .into_styled(marker_primitive_style)
323 .draw(target)
324 .map_err(|_| ChartError::RenderingError)?;
325 }
326 }
327 }
328
329 Ok(())
330 }
331}
332
333#[derive(Debug)]
335pub struct CurveChartBuilder<C: PixelColor> {
336 line_builder: LineChartBuilder<C>,
338 interpolation_config: InterpolationConfig,
340}
341
342impl<C: PixelColor + 'static> CurveChartBuilder<C>
343where
344 C: From<embedded_graphics::pixelcolor::Rgb565>,
345{
346 pub fn new() -> Self {
348 Self {
349 line_builder: LineChartBuilder::new(),
350 interpolation_config: InterpolationConfig::default(),
351 }
352 }
353
354 pub fn interpolation_type(mut self, interpolation_type: InterpolationType) -> Self {
359 self.interpolation_config.interpolation_type = interpolation_type;
360 self
361 }
362
363 pub fn subdivisions(mut self, subdivisions: u32) -> Self {
371 self.interpolation_config.subdivisions = subdivisions.clamp(2, 32);
372 self
373 }
374
375 pub fn tension(mut self, tension: f32) -> Self {
380 self.interpolation_config.tension = tension.clamp(0.0, 1.0);
381 self
382 }
383
384 pub fn closed(mut self, closed: bool) -> Self {
389 self.interpolation_config.closed = closed;
390 self
391 }
392
393 pub fn line_color(mut self, color: C) -> Self {
395 self.line_builder = self.line_builder.line_color(color);
396 self
397 }
398
399 pub fn line_width(mut self, width: u32) -> Self {
401 self.line_builder = self.line_builder.line_width(width);
402 self
403 }
404
405 pub fn fill_area(mut self, color: C) -> Self {
407 self.line_builder = self.line_builder.fill_area(color);
408 self
409 }
410
411 pub fn with_markers(mut self, marker_style: MarkerStyle<C>) -> Self {
415 self.line_builder = self.line_builder.with_markers(marker_style);
416 self
417 }
418
419 pub fn with_title(mut self, title: &str) -> Self {
421 self.line_builder = self.line_builder.with_title(title);
422 self
423 }
424
425 pub fn background_color(mut self, color: C) -> Self {
427 self.line_builder = self.line_builder.background_color(color);
428 self
429 }
430
431 pub fn margins(mut self, margins: crate::chart::traits::Margins) -> Self {
433 self.line_builder = self.line_builder.margins(margins);
434 self
435 }
436
437 pub fn with_grid(mut self, grid: crate::grid::GridSystem<C>) -> Self {
439 self.line_builder = self.line_builder.with_grid(grid);
440 self
441 }
442
443 pub fn with_x_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
445 self.line_builder = self.line_builder.with_x_axis(axis);
446 self
447 }
448
449 pub fn with_y_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
451 self.line_builder = self.line_builder.with_y_axis(axis);
452 self
453 }
454
455 pub fn build(self) -> ChartResult<CurveChart<C>> {
457 let base_chart = self.line_builder.build()?;
458
459 Ok(CurveChart {
460 base_chart,
461 interpolation_config: self.interpolation_config,
462 })
463 }
464}
465
466impl<C: PixelColor + 'static> Default for CurveChartBuilder<C>
467where
468 C: From<embedded_graphics::pixelcolor::Rgb565>,
469{
470 fn default() -> Self {
471 Self::new()
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use embedded_graphics::pixelcolor::Rgb565;
479
480 #[test]
481 fn test_curve_chart_creation() {
482 let chart: CurveChart<Rgb565> = CurveChart::new();
483 assert_eq!(
484 chart.interpolation_config().interpolation_type,
485 InterpolationType::CubicSpline
486 );
487 assert_eq!(chart.interpolation_config().subdivisions, 8);
488 }
489
490 #[test]
491 fn test_curve_chart_builder() {
492 let chart: CurveChart<Rgb565> = CurveChart::builder()
493 .line_color(Rgb565::RED)
494 .line_width(3)
495 .interpolation_type(InterpolationType::Bezier)
496 .subdivisions(12)
497 .tension(0.8)
498 .build()
499 .unwrap();
500
501 assert_eq!(chart.style().line_color, Rgb565::RED);
502 assert_eq!(chart.style().line_width, 3);
503 assert_eq!(
504 chart.interpolation_config().interpolation_type,
505 InterpolationType::Bezier
506 );
507 assert_eq!(chart.interpolation_config().subdivisions, 12);
508 }
509
510 #[test]
511 fn test_interpolation_config_clamping() {
512 let chart: CurveChart<Rgb565> = CurveChart::builder()
513 .subdivisions(100) .tension(2.0) .build()
516 .unwrap();
517
518 assert_eq!(chart.interpolation_config().subdivisions, 32);
519 assert_eq!(chart.interpolation_config().tension, 1.0);
520 }
521}