From 780b6c1a97fb698a698a29ffcdc6364fdb67af93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20=C5=81ukasiuk?= Date: Tue, 19 May 2026 14:02:07 +0200 Subject: [PATCH 1/4] Add one liner plot function --- Cargo.toml | 2 ++ utilities/Cargo.toml | 2 ++ utilities/src/lib.rs | 1 + 3 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a6f16e7..d8b7c4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ resolver = "2" [workspace.dependencies] serde = { version = "1.0.228", features = ["derive"] } +eframe = { version = "0.31" } +egui_plot = { version = "0.31" } diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml index 5898309..0b26319 100644 --- a/utilities/Cargo.toml +++ b/utilities/Cargo.toml @@ -5,3 +5,5 @@ edition = "2024" [dependencies] serde = { workspace = true } +eframe = { workspace = true } +egui_plot = { workspace = true } \ No newline at end of file diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index 4fb6cfe..d5cffa2 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -1 +1,2 @@ pub mod lowpass; +pub mod plot; From 03d87d0f7f9138a4d2be396625d2ab2e458d48f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20=C5=81ukasiuk?= Date: Tue, 19 May 2026 14:02:56 +0200 Subject: [PATCH 2/4] Add plot example and plot.rs file --- utilities/examples/plot.rs | 12 +++++++++ utilities/src/plot.rs | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 utilities/examples/plot.rs create mode 100644 utilities/src/plot.rs diff --git a/utilities/examples/plot.rs b/utilities/examples/plot.rs new file mode 100644 index 0000000..5c46abe --- /dev/null +++ b/utilities/examples/plot.rs @@ -0,0 +1,12 @@ +use utilities::plot; + +fn main() { + let data: Vec<[f64; 2]> = (0..100) + .map(|i| { + let x = i as f64 * 0.1; + [x, x.sin()] + }) + .collect(); + + plot(data); +} diff --git a/utilities/src/plot.rs b/utilities/src/plot.rs new file mode 100644 index 0000000..67f052c --- /dev/null +++ b/utilities/src/plot.rs @@ -0,0 +1,54 @@ +//! Interactive line-plot utility built on `egui`. +//! +//! This module provides a small native plotting helper for manual inspection of +//! numeric data. It opens an `eframe` window and renders a single line series +//! from points represented as `[x, y]`. +//! +//! The implementation is intended for interactive use from binaries or example +//! programs rather than automated tests. Typical usage is to prepare a vector +//! of points and pass it to [`plot`]. +//! +//! # Examples +//! +//! ```no_run +//! use utilities::plot; +//! +//! let data: Vec<[f64; 2]> = (0..100) +//! .map(|i| { +//! let x = i as f64 * 0.1; +//! [x, x.sin()] +//! }) +//! .collect(); +//! +//! plot(data); +//! ``` + +use eframe::egui; +use egui_plot::{Line, Plot, PlotPoints}; + +/// Render `data` as a line plot in a native window. +pub fn plot(data: Vec<[f64; 2]>) { + eframe::run_native( + "plot", + eframe::NativeOptions::default(), + Box::new(|_| Ok(Box::new(PlotApp { data }))), + ) + .expect("Failed to open plot window"); +} + +struct PlotApp { + data: Vec<[f64; 2]>, +} + +impl eframe::App for PlotApp { + fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { + if ctx.input(|i| i.key_pressed(egui::Key::Escape) || !i.keys_down.is_empty()) { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + egui::CentralPanel::default().show(ctx, |ui| { + Plot::new("p").show(ui, |pu| { + pu.line(Line::new(PlotPoints::from_iter(self.data.iter().copied()))); + }); + }); + } +} From 4d708ee23b47af81ea5dfd6fc6ab4b6780d1b732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20=C5=81ukasiuk?= Date: Tue, 19 May 2026 17:28:41 +0200 Subject: [PATCH 3/4] Add two more function to draw plot with indexed values, and plot with multiple time series --- utilities/examples/plot.rs | 12 ---- utilities/examples/plot_example.rs | 13 ++++ utilities/src/plot.rs | 107 ++++++++++++++++++++++++----- 3 files changed, 102 insertions(+), 30 deletions(-) delete mode 100644 utilities/examples/plot.rs create mode 100644 utilities/examples/plot_example.rs diff --git a/utilities/examples/plot.rs b/utilities/examples/plot.rs deleted file mode 100644 index 5c46abe..0000000 --- a/utilities/examples/plot.rs +++ /dev/null @@ -1,12 +0,0 @@ -use utilities::plot; - -fn main() { - let data: Vec<[f64; 2]> = (0..100) - .map(|i| { - let x = i as f64 * 0.1; - [x, x.sin()] - }) - .collect(); - - plot(data); -} diff --git a/utilities/examples/plot_example.rs b/utilities/examples/plot_example.rs new file mode 100644 index 0000000..f55ae75 --- /dev/null +++ b/utilities/examples/plot_example.rs @@ -0,0 +1,13 @@ +use utilities::plot::plot_series; + +fn main() { + let mut sin = Vec::with_capacity(100); + let mut cos = Vec::with_capacity(100); + for i in 0..100 { + let x = i as f64 * 0.1; + sin.push([x, x.sin()]); + cos.push([x, x.cos()]); + } + + plot_series(&[("sin", &sin), ("cos", &cos)]); +} diff --git a/utilities/src/plot.rs b/utilities/src/plot.rs index 67f052c..9f6e439 100644 --- a/utilities/src/plot.rs +++ b/utilities/src/plot.rs @@ -1,33 +1,62 @@ //! Interactive line-plot utility built on `egui`. //! //! This module provides a small native plotting helper for manual inspection of -//! numeric data. It opens an `eframe` window and renders a single line series -//! from points represented as `[x, y]`. +//! numeric data. It opens an `eframe` window and renders one or more line +//! series in a native window. //! -//! The implementation is intended for interactive use from binaries or example -//! programs rather than automated tests. Typical usage is to prepare a vector -//! of points and pass it to [`plot`]. +//! Use [`plot`] for a single series of `[x, y]` points, [`plot_indexed`] for a +//! single series of sampled values plotted against their indices, or +//! [`plot_series`] for multiple named series on the same plot. //! //! # Examples //! //! ```no_run //! use utilities::plot; //! -//! let data: Vec<[f64; 2]> = (0..100) -//! .map(|i| { -//! let x = i as f64 * 0.1; -//! [x, x.sin()] -//! }) -//! .collect(); +//! let mut data = Vec::with_capacity(100); +//! for i in 0..100 { +//! let x = i as f64 * 0.1; +//! data.push([x, x.sin()]); +//! } //! -//! plot(data); +//! plot(&data); +//! ``` +//! +//! ```no_run +//! use utilities::plot::plot_indexed; +//! +//! let mut data = Vec::with_capacity(100); +//! for i in 0..100 { +//! let x = i as f64 * 0.1; +//! data.push(x.sin()); +//! } +//! +//! plot_indexed(&data); +//! ``` +//! +//! ```no_run +//! use utilities::plot::plot_series; +//! +//! let mut sin = Vec::with_capacity(100); +//! let mut cos = Vec::with_capacity(100); +//! for i in 0..100 { +//! let x = i as f64 * 0.1; +//! sin.push([x, x.sin()]); +//! cos.push([x, x.cos()]); +//! } +//! +//! plot_series(&[("sin", &sin), ("cos", &cos)]); //! ``` use eframe::egui; -use egui_plot::{Line, Plot, PlotPoints}; +use egui_plot::{Legend, Line, Plot, PlotPoints}; -/// Render `data` as a line plot in a native window. -pub fn plot(data: Vec<[f64; 2]>) { +/// Render a single series in a native window, where each point is `[x, y]`. +pub fn plot(data: &[[f64; 2]]) { + let data = vec![Series { + name: String::new(), + points: data.to_vec(), + }]; eframe::run_native( "plot", eframe::NativeOptions::default(), @@ -36,8 +65,39 @@ pub fn plot(data: Vec<[f64; 2]>) { .expect("Failed to open plot window"); } +/// Render a single series of sampled values using the sample index as `x`. +pub fn plot_indexed(data: &[f64]) { + let mut points = Vec::with_capacity(data.len()); + for (i, &y) in data.iter().enumerate() { + points.push([i as f64, y]); + } + plot(&points); +} + +/// Render multiple named `[x, y]` series on the same plot. +pub fn plot_series(series: &[(&str, &[[f64; 2]])]) { + let mut data = Vec::with_capacity(series.len()); + for (name, points) in series { + data.push(Series { + name: (*name).to_string(), + points: points.to_vec(), + }); + } + eframe::run_native( + "plot", + eframe::NativeOptions::default(), + Box::new(|_| Ok(Box::new(PlotApp { data }))), + ) + .expect("Failed to open plot window"); +} + +struct Series { + name: String, + points: Vec<[f64; 2]>, +} + struct PlotApp { - data: Vec<[f64; 2]>, + data: Vec, } impl eframe::App for PlotApp { @@ -46,8 +106,19 @@ impl eframe::App for PlotApp { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } egui::CentralPanel::default().show(ctx, |ui| { - Plot::new("p").show(ui, |pu| { - pu.line(Line::new(PlotPoints::from_iter(self.data.iter().copied()))); + let mut plot = Plot::new("p"); + if self.data.len() > 1 { + plot = plot.legend(Legend::default()); + } + plot.show(ui, |pu| { + for series in &self.data { + let mut line = + Line::new(PlotPoints::from_iter(series.points.iter().copied())); + if !series.name.is_empty() { + line = line.name(&series.name); + } + pu.line(line); + } }); }); } From bc7deb3da8e9df82cf162701daaf6c87399d6899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20=C5=81ukasiuk?= Date: Tue, 19 May 2026 17:41:04 +0200 Subject: [PATCH 4/4] Apply cargo fmt --- utilities/src/plot.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utilities/src/plot.rs b/utilities/src/plot.rs index 9f6e439..a133a4e 100644 --- a/utilities/src/plot.rs +++ b/utilities/src/plot.rs @@ -112,8 +112,7 @@ impl eframe::App for PlotApp { } plot.show(ui, |pu| { for series in &self.data { - let mut line = - Line::new(PlotPoints::from_iter(series.points.iter().copied())); + let mut line = Line::new(PlotPoints::from_iter(series.points.iter().copied())); if !series.name.is_empty() { line = line.name(&series.name); }