#![cfg(feature = "std")]
use embedded_charts::{
chart::{Chart, ChartBuilder, ChartConfig, LineChart},
data::{
aggregation::{
AggregationConfig, AggregationStrategy, DataAggregation, DownsamplingConfig,
},
DataSeries, Point2D, StaticDataSeries,
},
};
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::*,
primitives::{Circle, PrimitiveStyle, Rectangle},
};
use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay, Window};
fn generate_large_dataset() -> StaticDataSeries<Point2D, 1024> {
let mut series = StaticDataSeries::new();
for i in 0..1000 {
let x = i as f32 * 0.1;
let trend = 20.0 * (x * 0.05).sin();
let oscillation = 10.0 * (x * 0.5).sin();
let noise = 3.0 * (x * 5.0).sin() * (x * 7.0).cos();
let spike = if i % 200 == 0 { 15.0 } else { 0.0 };
let y = 50.0 + trend + oscillation + noise + spike;
series.push(Point2D::new(x, y)).unwrap();
}
series
}
fn generate_sensor_data() -> StaticDataSeries<Point2D, 256> {
let mut series = StaticDataSeries::new();
for i in 0..250 {
let time = i as f32 * 0.01;
let daily = 5.0 * (time * 0.1).cos();
let noise = 0.5 * (time * 50.0).sin() * (time * 73.0).cos();
let drift = time * 0.02;
let temperature = 22.0 + daily + noise + drift;
series.push(Point2D::new(time, temperature)).unwrap();
}
series
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut display = SimulatorDisplay::<Rgb565>::new(Size::new(1200, 800));
display.clear(Rgb565::WHITE)?;
let chart_areas = [
Rectangle::new(Point::new(50, 50), Size::new(500, 160)), Rectangle::new(Point::new(650, 50), Size::new(500, 160)), Rectangle::new(Point::new(50, 270), Size::new(500, 160)), Rectangle::new(Point::new(650, 270), Size::new(500, 160)), Rectangle::new(Point::new(50, 490), Size::new(500, 160)), Rectangle::new(Point::new(650, 490), Size::new(500, 160)), ];
{
let original_data = generate_large_dataset();
println!("Original dataset: {} points", original_data.len());
let mut truncated: StaticDataSeries<Point2D, 256> = StaticDataSeries::new();
for (i, point) in original_data.iter().enumerate() {
if i < 250 {
truncated.push(point)?;
} else {
break;
}
}
let chart = LineChart::<Rgb565>::builder()
.line_color(Rgb565::BLUE)
.line_width(1)
.with_title("Original (250/1000 pts)")
.build()?;
let config = ChartConfig::default();
chart.draw(&truncated, &config, chart_areas[0], &mut display)?;
let mean_config = AggregationConfig {
strategy: AggregationStrategy::Mean,
target_points: 50,
preserve_endpoints: true,
..Default::default()
};
let mean_aggregated: StaticDataSeries<Point2D, 256> =
original_data.aggregate(&mean_config)?;
println!("Mean aggregated: {} points", mean_aggregated.len());
let chart = LineChart::<Rgb565>::builder()
.line_color(Rgb565::GREEN)
.line_width(2)
.with_title("Mean Aggregation (50 pts)")
.build()?;
chart.draw(&mean_aggregated, &config, chart_areas[1], &mut display)?;
let minmax_config = AggregationConfig {
strategy: AggregationStrategy::MinMax,
target_points: 50,
preserve_endpoints: true,
..Default::default()
};
let minmax_aggregated: StaticDataSeries<Point2D, 256> =
original_data.aggregate(&minmax_config)?;
println!("MinMax aggregated: {} points", minmax_aggregated.len());
let chart = LineChart::<Rgb565>::builder()
.line_color(Rgb565::RED)
.line_width(2)
.with_title("MinMax Aggregation (50 pts)")
.build()?;
chart.draw(&minmax_aggregated, &config, chart_areas[2], &mut display)?;
let lttb_config = DownsamplingConfig {
max_points: 50,
preserve_endpoints: true,
min_reduction_ratio: 1.0,
};
let lttb_downsampled: StaticDataSeries<Point2D, 256> =
original_data.downsample_lttb(<tb_config)?;
println!("LTTB downsampled: {} points", lttb_downsampled.len());
let chart = LineChart::<Rgb565>::builder()
.line_color(Rgb565::MAGENTA)
.line_width(2)
.with_title("LTTB Downsampling (50 pts)")
.build()?;
chart.draw(<tb_downsampled, &config, chart_areas[3], &mut display)?;
}
{
let sensor_data = generate_sensor_data();
println!("Sensor data: {} points", sensor_data.len());
let chart = LineChart::<Rgb565>::builder()
.line_color(Rgb565::BLUE)
.line_width(1)
.with_title("High-Freq Sensor (250 pts)")
.build()?;
let config = ChartConfig::default();
chart.draw(&sensor_data, &config, chart_areas[4], &mut display)?;
let uniform_config = DownsamplingConfig {
max_points: 40,
preserve_endpoints: true,
min_reduction_ratio: 1.0,
};
let uniform_downsampled: StaticDataSeries<Point2D, 256> =
sensor_data.downsample_uniform(&uniform_config)?;
println!("Uniform downsampled: {} points", uniform_downsampled.len());
let chart = LineChart::<Rgb565>::builder()
.line_color(Rgb565::CYAN)
.line_width(2)
.with_title("Uniform Downsampling (40 pts)")
.build()?;
chart.draw(&uniform_downsampled, &config, chart_areas[5], &mut display)?;
}
let label_positions = [
Point::new(50, 230),
Point::new(650, 230),
Point::new(50, 450),
Point::new(650, 450),
Point::new(50, 670),
Point::new(650, 670),
];
let labels = [
"Shows first 250 of 1000 points",
"Smooth, preserves trends",
"Preserves peaks and valleys",
"Best visual preservation",
"Raw high-freq sensor data",
"Reduced while preserving trend",
];
for (pos, label) in label_positions.iter().zip(labels.iter()) {
Circle::with_center(*pos, 3)
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
.draw(&mut display)?;
println!("Label at {pos:?}: {label}");
}
println!("\n=== Performance Comparison ===");
println!("Original data: 1000 points");
println!("Mean aggregation: 20x reduction, preserves overall trend");
println!("MinMax aggregation: 20x reduction, preserves extremes");
println!("LTTB downsampling: 20x reduction, best visual fidelity");
println!("Uniform downsampling: 6x reduction, fastest processing");
let output_settings = OutputSettingsBuilder::new().build();
Window::new("Data Aggregation & Downsampling Demo", &output_settings).show_static(&display);
Ok(())
}