diff --git a/rs/plotting/Cargo.toml b/rs/plotting/Cargo.toml index 4856541b..02e4a22c 100644 --- a/rs/plotting/Cargo.toml +++ b/rs/plotting/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] clap = { version = "4", features = ["derive"] } -eframe = "0.22" +eframe = "0.23" +egui_plot = "0.23" prost = "0.12" tokio = { version = "1", features = ["full"] } tonic = "0.10.2" diff --git a/rs/plotting/src/actors/grpc_source.rs b/rs/plotting/src/actors/grpc_source.rs index 31b0df40..0318722f 100644 --- a/rs/plotting/src/actors/grpc_source.rs +++ b/rs/plotting/src/actors/grpc_source.rs @@ -137,8 +137,10 @@ impl ActorNode for ActiveGrpcSourceNodeImpl { .add_service(PlottingWidgetServer::new(server)) .serve(address.parse().unwrap()); match server_future.await { - Ok(_) =>{} - Err(e) =>{panic!("{}", e);} + Ok(_) => {} + Err(e) => { + panic!("{}", e); + } } } } diff --git a/rs/plotting/src/actors/plotter.rs b/rs/plotting/src/actors/plotter.rs index 8752d75d..9e9cd3bf 100644 --- a/rs/plotting/src/actors/plotter.rs +++ b/rs/plotting/src/actors/plotter.rs @@ -9,12 +9,9 @@ use hollywood::core::*; use hollywood::macros::actor_inputs; use tokio::select; -#[derive(Clone, Debug)] -#[derive(Default)] +#[derive(Clone, Debug, Default)] pub struct PlotterProp {} - - /// Inbound message for the Plotter actor. #[derive(Clone, Debug)] #[actor_inputs(PlotterInbound, {PlotterProp, PlotterState, NullOutbound})] diff --git a/rs/plotting/src/bin/plotter_example.rs b/rs/plotting/src/bin/plotter_example.rs index cd6d88da..16fac74e 100644 --- a/rs/plotting/src/bin/plotter_example.rs +++ b/rs/plotting/src/bin/plotter_example.rs @@ -3,6 +3,7 @@ pub use hollywood::core::*; use hollywood::macros::*; use plotting::actors::plotter::{run_on_main_thread, PlotterActor, PlotterProp, PlotterState}; +use plotting::graphs::common::{Bounds, OrdinateBounds}; use plotting::graphs::packets::PlottingPacket; use plotting::graphs::packets::PlottingPackets; @@ -37,25 +38,34 @@ impl OnMessage for GraphGeneratorMessage { ) { match &self { GraphGeneratorMessage::ClockTick(time_in_seconds) => { - let mut packets = vec![]; - packets.push(PlottingPacket::append_to_curve( - ("graph", "sin"), - plotting::graphs::common::Color::red(), - (*time_in_seconds, time_in_seconds.sin()), - 10.0, - )); - packets.push(PlottingPacket::append_to_vec3_curve( - ("graph2", "sins"), - ( - *time_in_seconds, + let packets = vec![ + PlottingPacket::append_to_curve( + ("trig0", "sin"), + plotting::graphs::common::Color::red(), + (*time_in_seconds, time_in_seconds.sin()), + 1000.0, + Bounds { + x_bounds: OrdinateBounds::from_len_and_max(2.0, None), + y_bounds: OrdinateBounds::from_len_and_max(2.0, Some(1.0)), + }, + ), + PlottingPacket::append_to_vec3_curve( + ("trig1", "sin/cos/tan"), ( - time_in_seconds.cos(), - (2.0 * time_in_seconds).cos(), - (3.0 * time_in_seconds).cos(), + *time_in_seconds, + ( + time_in_seconds.sin(), + time_in_seconds.cos(), + time_in_seconds.tan(), + ), ), + 1000.0, + Bounds { + x_bounds: OrdinateBounds::from_len_and_max(2.0, None), + y_bounds: OrdinateBounds::from_len_and_max(2.0, Some(1.0)), + }, ), - 10.0, - )); + ]; outbound.packets.send(packets); } } diff --git a/rs/plotting/src/graphs/common.rs b/rs/plotting/src/graphs/common.rs index e7a3b0d6..16827d42 100644 --- a/rs/plotting/src/graphs/common.rs +++ b/rs/plotting/src/graphs/common.rs @@ -44,6 +44,34 @@ impl Color { } } +#[derive(Clone, Debug, Copy)] +pub struct RegionF64 { + pub min: f64, + pub max: f64, +} +#[derive(Clone, Debug, Copy)] +pub struct OrdinateBounds { + pub largest: f64, + pub len: f64, + pub data_driven: bool, +} + +impl OrdinateBounds { + pub fn from_len_and_max(len: f64, max: Option) -> Self { + OrdinateBounds { + largest: max.unwrap_or_default(), + len, + data_driven: max.is_none(), + } + } +} + +#[derive(Clone, Debug, Copy)] +pub struct Bounds { + pub x_bounds: OrdinateBounds, + pub y_bounds: OrdinateBounds, +} + #[derive(Clone, Debug, Default)] pub enum LineType { #[default] diff --git a/rs/plotting/src/graphs/packets.rs b/rs/plotting/src/graphs/packets.rs index f66a1e26..3a87fe73 100644 --- a/rs/plotting/src/graphs/packets.rs +++ b/rs/plotting/src/graphs/packets.rs @@ -1,7 +1,5 @@ - - use super::{ - common::{Color, LineType, ResetPredicate}, + common::{Bounds, Color, LineType, ResetPredicate}, scalar_curve::{NamedScalarCurve, ScalarCurve}, vec3_curve::{NamedVec3Curve, Vec3Curve}, }; @@ -20,6 +18,7 @@ impl PlottingPacket { color: Color, (x, y): (f64, f64), history_length: f64, + bounds: Bounds, ) -> PlottingPacket { let curve = NamedScalarCurve { plot_name: plot.into(), @@ -31,6 +30,7 @@ impl PlottingPacket { clear_x_smaller_than: ResetPredicate { clear_x_smaller_than: Some(x - history_length), }, + bounds, }, }; @@ -41,6 +41,7 @@ impl PlottingPacket { (plot, graph): (S, S), (x, y): (f64, (f64, f64, f64)), history_length: f64, + bounds: Bounds, ) -> PlottingPacket { let curve = NamedVec3Curve { plot_name: plot.into(), @@ -52,6 +53,7 @@ impl PlottingPacket { clear_x_smaller_than: ResetPredicate { clear_x_smaller_than: Some(x - history_length), }, + bounds, }, }; diff --git a/rs/plotting/src/graphs/scalar_curve.rs b/rs/plotting/src/graphs/scalar_curve.rs index a21954f6..fd022198 100644 --- a/rs/plotting/src/graphs/scalar_curve.rs +++ b/rs/plotting/src/graphs/scalar_curve.rs @@ -1,11 +1,14 @@ use crate::graphs::common::{Color, LineType, ResetPredicate}; -#[derive(Clone, Debug, Default)] +use super::common::Bounds; + +#[derive(Clone, Debug)] pub struct ScalarCurve { pub data: Vec<(f64, f64)>, pub color: Color, pub curve_type: LineType, pub clear_x_smaller_than: ResetPredicate, + pub bounds: Bounds, } impl ScalarCurve { @@ -14,12 +17,14 @@ impl ScalarCurve { color: Color, curve_type: LineType, f: ResetPredicate, + bounds: Bounds, ) -> Self { ScalarCurve { data, color, curve_type, clear_x_smaller_than: f, + bounds, } } @@ -52,7 +57,7 @@ impl ScalarCurve { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct NamedScalarCurve { pub plot_name: String, pub graph_name: String, diff --git a/rs/plotting/src/graphs/vec3_curve.rs b/rs/plotting/src/graphs/vec3_curve.rs index 68564fc3..0d99b114 100644 --- a/rs/plotting/src/graphs/vec3_curve.rs +++ b/rs/plotting/src/graphs/vec3_curve.rs @@ -1,11 +1,14 @@ use crate::graphs::common::{Color, LineType, ResetPredicate}; -#[derive(Clone, Debug, Default)] +use super::common::Bounds; + +#[derive(Clone, Debug)] pub struct Vec3Curve { pub data: Vec<(f64, (f64, f64, f64))>, pub color: [Color; 3], pub curve_type: LineType, pub clear_x_smaller_than: ResetPredicate, + pub bounds: Bounds, } impl Vec3Curve { @@ -14,12 +17,14 @@ impl Vec3Curve { color: [Color; 3], curve_type: LineType, f: ResetPredicate, + bounds: Bounds, ) -> Self { Vec3Curve { data, color, curve_type, clear_x_smaller_than: f, + bounds, } } @@ -52,7 +57,7 @@ impl Vec3Curve { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct NamedVec3Curve { pub plot_name: String, pub graph_name: String, diff --git a/rs/plotting/src/grpc/proto.rs b/rs/plotting/src/grpc/proto.rs index c2c58270..5a4ee069 100644 --- a/rs/plotting/src/grpc/proto.rs +++ b/rs/plotting/src/grpc/proto.rs @@ -1,6 +1,6 @@ use std::mem::size_of; -use crate::graphs::common::{LineType, ResetPredicate}; +use crate::graphs::common::{Bounds, LineType, OrdinateBounds, ResetPredicate}; use crate::graphs::packets::{PlottingPacket, PlottingPackets}; use crate::graphs::scalar_curve::{NamedScalarCurve, ScalarCurve}; use crate::graphs::vec3_curve::{NamedVec3Curve, Vec3Curve}; @@ -89,6 +89,18 @@ pub fn from_proto(m: Messages) -> PlottingPackets { clear_x_smaller_than: reset_predicate_from_proto( proto_curve.reset.unwrap(), ), + bounds: Bounds { + x_bounds: OrdinateBounds { + largest: 0.0, + len: 100.0, + data_driven: true, + }, + y_bounds: OrdinateBounds { + largest: 0.0, + len: 100.0, + data_driven: true, + }, + }, }, })); } @@ -143,6 +155,18 @@ pub fn from_proto(m: Messages) -> PlottingPackets { clear_x_smaller_than: reset_predicate_from_proto( proto_curve3.reset.unwrap(), ), + bounds: Bounds { + x_bounds: OrdinateBounds { + largest: 0.0, + len: 100.0, + data_driven: true, + }, + y_bounds: OrdinateBounds { + largest: 0.0, + len: 100.0, + data_driven: true, + }, + }, }, })); } diff --git a/rs/plotting/src/plotter_gui/mod.rs b/rs/plotting/src/plotter_gui/mod.rs index eaa9d62e..475e7fbd 100644 --- a/rs/plotting/src/plotter_gui/mod.rs +++ b/rs/plotting/src/plotter_gui/mod.rs @@ -1,11 +1,14 @@ +use std::ops::RangeInclusive; + use eframe::egui; + use hollywood::compute::pipeline::CancelRequest; +use crate::graphs::common::Bounds; +use crate::graphs::packets::{PlottingPacket, PlottingPackets}; use crate::graphs::scalar_curve::ScalarCurve; use crate::graphs::vec3_curve::Vec3Curve; -use crate::graphs::packets::{PlottingPacket, PlottingPackets}; - /// This is the gui app of the plotting service. pub struct PlotterGuiState { pub receiver: std::sync::mpsc::Receiver, @@ -13,10 +16,9 @@ pub struct PlotterGuiState { /// holds a collection of plots indexed by plot_name pub plots: std::collections::BTreeMap, - toggle_plot: std::collections::BTreeMap, selected_plot: String, - show_axis: [bool; 3], + first_plot_received: bool, } impl From for egui::Color32 { @@ -39,15 +41,17 @@ pub enum GraphType { #[derive(Clone, Debug)] pub struct CurveStruct { pub curve: GraphType, - pub toggle: bool, + pub show_graph: bool, } /// a single plot is a collection of curves, indexed by curve_name #[derive(Clone, Debug)] pub struct Plot { pub curves: std::collections::BTreeMap, - pub x_range: Option<(f64, f64)>, - pub y_range: Option<(f64, f64)>, + pub bounds: Bounds, + pub show_plot: bool, + pub mouse_nav: bool, + pub show_axis: [bool; 3], } impl PlotterGuiState { @@ -59,9 +63,8 @@ impl PlotterGuiState { receiver, cancel_requester, plots: std::collections::BTreeMap::new(), - toggle_plot: std::collections::BTreeMap::new(), - show_axis: [true; 3], selected_plot: String::new(), + first_plot_received: false, } } } @@ -84,9 +87,12 @@ impl eframe::App for PlotterGuiState { let plot = self.plots.entry(plot_name.clone()).or_insert(Plot { curves: std::collections::BTreeMap::new(), - x_range: None, - y_range: None, + bounds: new_value.scalar_curve.bounds, + show_plot: !self.first_plot_received, + mouse_nav: false, + show_axis: [true, true, true], }); + self.first_plot_received = true; plot.curves .entry(curve_name.clone()) .and_modify(|curve_struct| match &mut curve_struct.curve { @@ -101,11 +107,9 @@ impl eframe::App for PlotterGuiState { GraphType::Vec3(_g) => {} }) .or_insert(CurveStruct { - curve: GraphType::Scalar(new_value.scalar_curve), - toggle: true, + curve: GraphType::Scalar(new_value.scalar_curve.clone()), + show_graph: true, }); - - self.toggle_plot.insert(plot_name, true); } PlottingPacket::Vec3Curve(new_value) => { @@ -114,9 +118,12 @@ impl eframe::App for PlotterGuiState { let plot = self.plots.entry(plot_name.clone()).or_insert(Plot { curves: std::collections::BTreeMap::new(), - x_range: None, - y_range: None, + bounds: new_value.scalar_curve.bounds, + show_plot: !self.first_plot_received, + mouse_nav: false, + show_axis: [true, true, true], }); + self.first_plot_received = true; plot.curves .entry(curve_name.clone()) .and_modify(|curve_struct| match &mut curve_struct.curve { @@ -131,11 +138,9 @@ impl eframe::App for PlotterGuiState { } }) .or_insert(CurveStruct { - curve: GraphType::Vec3(new_value.scalar_curve), - toggle: true, + curve: GraphType::Vec3(new_value.scalar_curve.clone()), + show_graph: true, }); - - self.toggle_plot.insert(plot_name, true); } } } @@ -143,9 +148,9 @@ impl eframe::App for PlotterGuiState { egui::SidePanel::left("toggles").show(ctx, |ui| { { - for (plot_name, checked) in &mut self.toggle_plot { + for (plot_name, plot) in &mut self.plots { ui.horizontal(|ui| { - ui.checkbox(checked, ""); + ui.checkbox(&mut plot.show_plot, ""); if ui .add(egui::SelectableLabel::new( plot_name == &self.selected_plot, @@ -165,33 +170,71 @@ impl eframe::App for PlotterGuiState { if &self.selected_plot != plot_name { continue; } - ui.label(plot_name); + ui.label(format!("Graphs in '{}' plot", plot_name)); + for (curve_name, curve) in &mut plot.curves { - ui.checkbox(&mut curve.toggle, curve_name); + ui.checkbox(&mut curve.show_graph, curve_name); } - } - ui.separator(); + ui.separator(); + ui.label(format!("Bounds of '{}' plot", plot_name)); - // Toggle axes - { - for i in 0..3 { - let axis = &mut self.show_axis[i]; - ui.toggle_value(axis, format!("show axis-{}", i)); - } + ui.add_enabled_ui(!plot.mouse_nav, |ui| { + ui.horizontal_wrapped(|ui| { + ui.label("width"); + ui.add( + egui::DragValue::new(&mut plot.bounds.x_bounds.len) + .clamp_range(RangeInclusive::new(0.0, 10000.0)), + ); + }); + ui.horizontal_wrapped(|ui| { + ui.label("height"); + ui.add( + egui::DragValue::new(&mut plot.bounds.y_bounds.len) + .clamp_range(RangeInclusive::new(0.0, 10000.0)), + ); + }); + + ui.horizontal_wrapped(|ui| { + ui.add_enabled_ui(!plot.bounds.x_bounds.data_driven, |ui| { + ui.label("x-max"); + ui.add(egui::DragValue::new(&mut plot.bounds.x_bounds.largest)); + }); + ui.checkbox(&mut plot.bounds.x_bounds.data_driven, "data-driven"); + }); + + ui.horizontal_wrapped(|ui| { + ui.add_enabled_ui(!plot.bounds.y_bounds.data_driven, |ui| { + ui.label("y-max"); + ui.add(egui::DragValue::new(&mut plot.bounds.y_bounds.largest)); + }); + ui.checkbox(&mut plot.bounds.y_bounds.data_driven, "data-driven"); + }); + }); + ui.checkbox(&mut plot.mouse_nav, "mouse nav"); + + ui.separator(); - if ui.add(egui::Button::new("reset")).clicked() { - self.plots = std::collections::BTreeMap::new(); - self.toggle_plot = std::collections::BTreeMap::new(); + // Toggle axes + { + for i in 0..3 { + let axis = &mut plot.show_axis[i]; + ui.toggle_value(axis, format!("show axis-{}", i)); + } } } + ui.separator(); + + if ui.add(egui::Button::new("reset")).clicked() { + self.plots = std::collections::BTreeMap::new(); + } }); egui::CentralPanel::default().show(ctx, |ui| { // 1. Get the available height let h = ui.available_height(); - let num_checked = self.toggle_plot.values().filter(|x| **x).count(); + let num_checked = self.plots.values().filter(|x| x.show_plot).count(); // 2.Calculate the height per plot let height_per_plot = (h / num_checked as f32).floor(); @@ -199,42 +242,59 @@ impl eframe::App for PlotterGuiState { // 3. Plot each plot to the ui for (plot_name, plot_data) in &mut self.plots { - if !self.toggle_plot[plot_name] { + if !plot_data.show_plot { continue; } - egui::plot::Plot::new(plot_name) + egui_plot::Plot::new(plot_name) .height(height_per_plot) - .legend( - egui::plot::Legend::default() - .position(egui::widgets::plot::Corner::LeftTop), - ) + .legend(egui_plot::Legend::default().position(egui_plot::Corner::LeftTop)) .auto_bounds_x() .auto_bounds_y() .show(ui, |plot_ui| { + let mut data_driven_largest_x = -std::f64::INFINITY; + let mut data_driven_largest_y = -std::f64::INFINITY; + + if plot_ui.response().double_clicked() { + plot_data.mouse_nav = false; + self.selected_plot = plot_name.clone(); + } else if plot_ui.response().clicked() || plot_ui.response().dragged() { + plot_data.mouse_nav = true; + self.selected_plot = plot_name.clone(); + } + for (curve_name, graph_data) in &mut plot_data.curves { - if !graph_data.toggle { + if !graph_data.show_graph { continue; } + match &graph_data.curve { GraphType::Scalar(g) => { let mut points = vec![]; for (x, y) in &g.data { - points.push(egui::plot::PlotPoint::new(*x, *y)); + if x > &data_driven_largest_x { + data_driven_largest_x = *x; + } + + if y > &data_driven_largest_y { + data_driven_largest_y = *y; + } + + points.push(egui_plot::PlotPoint::new(*x, *y)); } - let plot_points = egui::plot::PlotPoints::Owned(points); + let plot_points = egui_plot::PlotPoints::Owned(points); match g.curve_type { crate::graphs::common::LineType::LineStrip => { plot_ui.line( - egui::plot::Line::new(plot_points) + egui_plot::Line::new(plot_points) .color(g.color) .name(curve_name), ); } crate::graphs::common::LineType::Points => { plot_ui.points( - egui::plot::Points::new(plot_points) + egui_plot::Points::new(plot_points) .color(g.color) .name(curve_name), ); @@ -242,50 +302,79 @@ impl eframe::App for PlotterGuiState { } } GraphType::Vec3(g) => { - let mut points = vec![]; - points.push(vec![]); - points.push(vec![]); - points.push(vec![]); + let mut points = vec![vec![], vec![], vec![]]; for (x, y) in &g.data { - points[0].push(egui::plot::PlotPoint::new(*x, y.0)); - points[1].push(egui::plot::PlotPoint::new(*x, y.1)); - points[2].push(egui::plot::PlotPoint::new(*x, y.2)); + if x > &data_driven_largest_x { + data_driven_largest_x = *x; + } + + let max_y = y.0.max(y.1.max(y.2)); + if max_y > data_driven_largest_y { + data_driven_largest_y = max_y; + } + + points[0].push(egui_plot::PlotPoint::new(*x, y.0)); + points[1].push(egui_plot::PlotPoint::new(*x, y.1)); + points[2].push(egui_plot::PlotPoint::new(*x, y.2)); } match g.curve_type { crate::graphs::common::LineType::LineStrip => { - for i in 0..3 { - let plot_points = egui::plot::PlotPoints::Owned( - points[i].clone(), - ); - plot_ui.line( - egui::plot::Line::new(plot_points) - .color(g.color[i]) - .name(format!("{}-{}", curve_name, i)), - ); + for (i, p) in points.iter().enumerate().take(3) { + if plot_data.show_axis[i] { + let plot_points = + egui_plot::PlotPoints::Owned(p.clone()); + plot_ui.line( + egui_plot::Line::new(plot_points) + .color(g.color[i]) + .name(format!("{}-{}", curve_name, i)), + ); + } } } crate::graphs::common::LineType::Points => { - for i in 0..3 { - let plot_points = egui::plot::PlotPoints::Owned( - points[i].clone(), - ); - plot_ui.line( - egui::plot::Line::new(plot_points) - .color(g.color[i]) - .name(format!("{}-{}", curve_name, i)), - ); + for (i, p) in points.iter().enumerate().take(3) { + if plot_data.show_axis[i] { + let plot_points = + egui_plot::PlotPoints::Owned(p.clone()); + plot_ui.line( + egui_plot::Line::new(plot_points) + .color(g.color[i]) + .name(format!("{}-{}", curve_name, i)), + ); + } } } } } } } + if !plot_data.mouse_nav { + let largest_x = if plot_data.bounds.x_bounds.data_driven { + data_driven_largest_x + } else { + plot_data.bounds.x_bounds.largest + }; + let largest_y = if plot_data.bounds.y_bounds.data_driven { + data_driven_largest_y + } else { + plot_data.bounds.y_bounds.largest + }; + plot_ui.set_plot_bounds(egui_plot::PlotBounds::from_min_max( + [ + largest_x - plot_data.bounds.x_bounds.len, + largest_y - plot_data.bounds.y_bounds.len, + ], + [largest_x, largest_y], + )); + plot_data.bounds.x_bounds.largest = largest_x; + plot_data.bounds.y_bounds.largest = largest_y; + } }); } }); - ctx.request_repaint_after(std::time::Duration::from_secs_f64(0.1)); + ctx.request_repaint_after(std::time::Duration::from_secs_f64(0.01)); } }