From 940a5fb91dd213f748864298b1b1884a01aaf17c Mon Sep 17 00:00:00 2001 From: Hauke Strasdat Date: Sat, 7 Oct 2023 22:37:31 -0700 Subject: [PATCH] feat: plot nav improvements --- .../core/plotting/remote_plot_example.cpp | 19 +- cpp/farm_ng/core/plotting/types.h | 42 +-- cpp/farm_ng/core/proto_conv/plotting/conv.cpp | 34 +- cpp/farm_ng/core/proto_conv/plotting/conv.h | 8 +- protos/farm_ng/core/plotting/plotting.proto | 68 ++-- rs/plotting/Cargo.toml | 3 +- rs/plotting/src/actors/grpc_source.rs | 6 +- rs/plotting/src/actors/plotter.rs | 5 +- rs/plotting/src/bin/plotter_example.rs | 42 ++- rs/plotting/src/graphs/common.rs | 54 +++ rs/plotting/src/graphs/mod.rs | 1 + rs/plotting/src/graphs/packets.rs | 10 +- rs/plotting/src/graphs/scalar_curve.rs | 9 +- rs/plotting/src/graphs/vec3_conf_curve.rs | 70 ++++ rs/plotting/src/graphs/vec3_curve.rs | 9 +- rs/plotting/src/grpc/proto.rs | 120 ++++++- rs/plotting/src/plotter_gui/mod.rs | 334 ++++++++++++++---- 17 files changed, 643 insertions(+), 191 deletions(-) create mode 100644 rs/plotting/src/graphs/vec3_conf_curve.rs diff --git a/cpp/farm_ng/core/plotting/remote_plot_example.cpp b/cpp/farm_ng/core/plotting/remote_plot_example.cpp index 1a7acad9..22a70461 100644 --- a/cpp/farm_ng/core/plotting/remote_plot_example.cpp +++ b/cpp/farm_ng/core/plotting/remote_plot_example.cpp @@ -29,6 +29,9 @@ int main(int argc, char* argv[]) { pool->start(8); plotting::Vec3Curve trig_graph; + trig_graph.bounds = { + .x_bounds = {.len = 5.0 * M_PI}, + .y_bounds = {.height = 2.0, .offset = 0.0}}; trig_graph.path = "trig/ {sin,cos,tan}"; trig_graph.color = { sophus::Color::neonRed(), @@ -36,6 +39,9 @@ int main(int argc, char* argv[]) { sophus::Color::neonBlue()}; plotting::Curve sawtooth; + sawtooth.bounds = { + .x_bounds = {.len = 5.0 * M_PI}, + .y_bounds = {.height = 2.0, .offset = 0.0}}; sawtooth.path = "trig1/sawtooth"; sawtooth.color = sophus::Color::blue(); @@ -44,14 +50,8 @@ int main(int argc, char* argv[]) { double x = 0.0; - auto x_range0 = - plotting::XRange{.range = {-2.0 * M_PI, 0.0}, .path = "trig0"}; - auto x_range1 = - plotting::XRange{.range = {-2.0 * M_PI, 0.0}, .path = "trig1"}; - std::vector messages; - messages.push_back(plotting::YRange{.range = {-1.0, 1.0}, .path = "trig0"}); - messages.push_back(plotting::YRange{.range = {-1.0, 1.0}, .path = "trig1"}); + plotting->inMessages().send(messages); int counter = 0; while (true) { @@ -74,9 +74,6 @@ int main(int argc, char* argv[]) { messages.push_back(trig_graph); messages.push_back(sawtooth); - x_range0.range = {x - 2.0 * M_PI, x}; - x_range1.range = {x - 2.0 * M_PI, x}; - ++counter; if (counter >= 10) { @@ -88,8 +85,6 @@ int main(int argc, char* argv[]) { messages.push_back(timestamps); } - messages.push_back(x_range0); - messages.push_back(x_range1); plotting->inMessages().send(messages); x += 0.01; diff --git a/cpp/farm_ng/core/plotting/types.h b/cpp/farm_ng/core/plotting/types.h index 488f9ecb..3f24ed51 100644 --- a/cpp/farm_ng/core/plotting/types.h +++ b/cpp/farm_ng/core/plotting/types.h @@ -46,11 +46,24 @@ struct CurveResetPredicate { std::optional clear_x_smaller_than = std::nullopt; }; +FARM_STRUCT( + XCoordinateBounds, + ((std::optional, max_x, {std::nullopt}), (double, len, {100.0}))); + +FARM_STRUCT( + YCoordinateBounds, + ((std::optional, height, {std::nullopt}), (double, offset, {0.0}))); + +FARM_STRUCT( + Bounds, + ((XCoordinateBounds, x_bounds, {}), (YCoordinateBounds, y_bounds, {}))); + // Append to an existing curve in a given plot (or add new one if it does not // exist already), with path being "plot_name/curve_name". FARM_STRUCT( Curve, - ((sophus::Color, color, {}), + ((Bounds, bounds, {}), + (sophus::Color, color, {}), (LineType, line_type, {LineType::line_strip}), (std::filesystem::path, path, {}), (CurveResetPredicate, reset, {}), @@ -74,7 +87,8 @@ ColorArray3 constexpr kDefaultConfColorArray3 = { // for each curve, and the remaining components are the y coordinates. FARM_STRUCT( Vec3Curve, - ((ColorArray3, color, {kDefaultColorArray3}), + ((Bounds, bounds, {}), + (ColorArray3, color, {kDefaultColorArray3}), (LineType, line_type, {LineType::line_strip}), (std::filesystem::path, path, {}), (CurveResetPredicate, reset, {}), @@ -87,7 +101,8 @@ using Vec7d = Eigen::Matrix; // standard deviation). FARM_STRUCT( Vec3CurveWithConfInterval, - ((ColorArray3, color, {kDefaultColorArray3}), + ((Bounds, bounds, {}), + (ColorArray3, color, {kDefaultColorArray3}), (ColorArray3, conf_color, {kDefaultConfColorArray3}), (LineType, line_type, {LineType::line_strip}), (std::filesystem::path, path, {}), @@ -133,26 +148,13 @@ struct ColoredRect { FARM_STRUCT( RectPlot, - ((std::filesystem::path, path, {}), + ((Bounds, bounds, {}), (std::deque, colored_rects, {}), + (std::filesystem::path, path, {}), (CurveResetPredicate, reset, {}))); -FARM_STRUCT( - XRange, - ((sophus::RegionF64, range, {sophus::RegionF64::empty()}), - (std::filesystem::path, path, {}))); -FARM_STRUCT( - YRange, - ((sophus::RegionF64, range, {sophus::RegionF64::empty()}), - (std::filesystem::path, path, {}))); - -using Message = std::variant< - RectPlot, - Curve, - Vec3Curve, - Vec3CurveWithConfInterval, - XRange, - YRange>; +using Message = + std::variant; } // namespace plotting diff --git a/cpp/farm_ng/core/proto_conv/plotting/conv.cpp b/cpp/farm_ng/core/proto_conv/plotting/conv.cpp index ba4fa4b3..87272b07 100644 --- a/cpp/farm_ng/core/proto_conv/plotting/conv.cpp +++ b/cpp/farm_ng/core/proto_conv/plotting/conv.cpp @@ -151,17 +151,17 @@ FARM_CONV_IMPL_REPEATED( FARM_PROTO_CONV_IMPL( plotting::RectPlot, core::plotting::proto::RectPlot, - (path, colored_rects, reset)); + (bounds, colored_rects, path, reset)); FARM_PROTO_CONV_IMPL( plotting::Curve, core::plotting::proto::Curve, - (color, line_type, path, reset, x_y_pairs)); + (bounds, color, line_type, path, reset, x_y_pairs)); FARM_PROTO_CONV_IMPL_FN( plotting::Vec3Curve, core::plotting::proto::Vec3Curve, - ((color, SKIP), line_type, path, reset, x_vec_pairs), + (bounds, (color, SKIP), line_type, path, reset, x_vec_pairs), [](plotting::Vec3Curve&& s, core::plotting::proto::Vec3Curve const& proto) -> Expected { if (proto.color_size() != 3) { @@ -183,7 +183,8 @@ FARM_PROTO_CONV_IMPL_FN( FARM_PROTO_CONV_IMPL_FN( plotting::Vec3CurveWithConfInterval, core::plotting::proto::Vec3CurveWithConfInterval, - ((color, SKIP), + (bounds, + (color, SKIP), (conf_color, SKIP), line_type, path, @@ -219,10 +220,17 @@ FARM_PROTO_CONV_IMPL_FN( }); FARM_PROTO_CONV_IMPL( - plotting::XRange, core::plotting::proto::XRange, (range, path)); + plotting::XCoordinateBounds, + core::plotting::proto::XCoordinateBounds, + (max_x, len)); FARM_PROTO_CONV_IMPL( - plotting::YRange, core::plotting::proto::YRange, (range, path)); + plotting::YCoordinateBounds, + core::plotting::proto::YCoordinateBounds, + (height, offset)); + +FARM_PROTO_CONV_IMPL( + plotting::Bounds, core::plotting::proto::Bounds, (x_bounds, y_bounds)); template <> auto fromProt( @@ -249,16 +257,6 @@ auto fromProt( msg = m; return msg; } - if (proto.has_x_range()) { - FARM_TRY(auto, m, fromProt(proto.x_range())); - msg = m; - return msg; - } - if (proto.has_y_range()) { - FARM_TRY(auto, m, fromProt(proto.y_range())); - msg = m; - return msg; - } return msg; } @@ -275,9 +273,7 @@ auto toProt(plotting::Message const& v) }, [&](plotting::Vec3CurveWithConfInterval const& v) { *proto.mutable_vec3_conf_curve() = toProt(v); - }, - [&](plotting::XRange const& v) { *proto.mutable_x_range() = toProt(v); }, - [&](plotting::YRange const& v) { *proto.mutable_y_range() = toProt(v); }); + }); return proto; } diff --git a/cpp/farm_ng/core/proto_conv/plotting/conv.h b/cpp/farm_ng/core/proto_conv/plotting/conv.h index 8d1136d7..4d16bdbb 100644 --- a/cpp/farm_ng/core/proto_conv/plotting/conv.h +++ b/cpp/farm_ng/core/proto_conv/plotting/conv.h @@ -45,8 +45,12 @@ FARM_PROTO_CONV_TRAIT(plotting::Vec3Curve, core::plotting::proto::Vec3Curve); FARM_PROTO_CONV_TRAIT( plotting::Vec3CurveWithConfInterval, core::plotting::proto::Vec3CurveWithConfInterval); -FARM_PROTO_CONV_TRAIT(plotting::XRange, core::plotting::proto::XRange); -FARM_PROTO_CONV_TRAIT(plotting::YRange, core::plotting::proto::YRange); + +FARM_PROTO_CONV_TRAIT( + plotting::XCoordinateBounds, core::plotting::proto::XCoordinateBounds); +FARM_PROTO_CONV_TRAIT( + plotting::YCoordinateBounds, core::plotting::proto::YCoordinateBounds); +FARM_PROTO_CONV_TRAIT(plotting::Bounds, core::plotting::proto::Bounds); FARM_PROTO_CONV_TRAIT(plotting::Message, core::plotting::proto::Message); FARM_PROTO_CONV_TRAIT( std::vector, core::plotting::proto::Messages); diff --git a/protos/farm_ng/core/plotting/plotting.proto b/protos/farm_ng/core/plotting/plotting.proto index ab21c50c..6ad02d25 100644 --- a/protos/farm_ng/core/plotting/plotting.proto +++ b/protos/farm_ng/core/plotting/plotting.proto @@ -46,31 +46,50 @@ message XVecConvTupleF64 { int64 num_tuples = 2; } +message XCoordinateBounds { + core.proto.OptionalG0Double max_x = 1; + double len = 2; +} + + +message YCoordinateBounds { + core.proto.OptionalG0Double height = 1; + double offset = 2; +} + +message Bounds { + XCoordinateBounds x_bounds = 1; + YCoordinateBounds y_bounds = 2; +} + message LineType { string variant_name = 1; } message Curve { - core.proto.Color color = 1; - LineType line_type = 2; - core.proto.FileSystemPath path = 3; - CurveResetPredicate reset = 4; - XyPairsF64 x_y_pairs = 5; + Bounds bounds = 1; + core.proto.Color color = 2; + LineType line_type = 3; + core.proto.FileSystemPath path = 4; + CurveResetPredicate reset = 5; + XyPairsF64 x_y_pairs = 6; }; message Vec3Curve { - repeated core.proto.Color color = 1; - LineType line_type = 2; - core.proto.FileSystemPath path = 3; - CurveResetPredicate reset = 4; - XVecTupleF64 x_vec_pairs = 5; -}; - -message Vec3CurveWithConfInterval { - repeated core.proto.Color color = 1; - repeated core.proto.Color conf_color = 2; + Bounds bounds = 1; + repeated core.proto.Color color = 2; LineType line_type = 3; core.proto.FileSystemPath path = 4; CurveResetPredicate reset = 5; - XVecConvTupleF64 x_vec_conf_tuples = 6; + XVecTupleF64 x_vec_pairs = 6; +}; + +message Vec3CurveWithConfInterval { + Bounds bounds = 1; + repeated core.proto.Color color = 2; + repeated core.proto.Color conf_color = 3; + LineType line_type = 4; + core.proto.FileSystemPath path = 5; + CurveResetPredicate reset = 6; + XVecConvTupleF64 x_vec_conf_tuples = 7; }; message ColoredRect { @@ -81,19 +100,10 @@ message ColoredRect { message RepeatedG0ColoredRect { repeated ColoredRect value = 1; } message RectPlot { - core.proto.FileSystemPath path = 1; + Bounds bounds = 1; RepeatedG0ColoredRect colored_rects = 2; - CurveResetPredicate reset = 3; -} - -message XRange { - core.proto.FileSystemPath path = 1; - core.proto.RegionF64 range = 2; -} - -message YRange { - core.proto.FileSystemPath path = 1; - core.proto.RegionF64 range = 2; + core.proto.FileSystemPath path = 3; + CurveResetPredicate reset = 4; } message Message { @@ -102,8 +112,6 @@ message Message { Vec3Curve vec3_curve = 2; Vec3CurveWithConfInterval vec3_conf_curve = 3; RectPlot rects = 4; - XRange x_range = 5; - YRange y_range = 6; } } 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..0a1c372d 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, XCoordinateBounds, YCoordinateBounds}; 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: XCoordinateBounds::from_len_and_max(2.0, None), + y_bounds: YCoordinateBounds::symmetric_from_height(Some(2.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: XCoordinateBounds::from_len_and_max(2.0, None), + y_bounds: YCoordinateBounds::symmetric_from_height(Some(2.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..e78a996f 100644 --- a/rs/plotting/src/graphs/common.rs +++ b/rs/plotting/src/graphs/common.rs @@ -44,6 +44,60 @@ impl Color { } } +#[derive(Clone, Debug, Copy)] +pub struct RegionF64 { + pub min: f64, + pub max: f64, +} + +#[derive(Clone, Debug, Copy)] +pub struct XCoordinateBounds { + /// max_x is derived from graph data + pub data_driven: bool, + /// largest x value / right bound of plot window + pub max_x: f64, + /// length of x axis, min_x = max_x - len + pub len: f64, +} + +impl XCoordinateBounds { + pub fn from_len_and_max(len: f64, max: Option) -> Self { + XCoordinateBounds { + data_driven: max.is_none(), + max_x: max.unwrap_or_default(), + len, + } + } +} + +#[derive(Clone, Debug, Copy)] +pub struct YCoordinateBounds { + /// height is derived from graph data + /// height = 0.5 * max{abs(y)} + pub data_driven: bool, + /// If offset is zero, then `height/2` is the top of the plot window + /// and `-height/2` is the bottom of the plot window + pub height: f64, + /// is this is zero, then the y axis is centered on zero + pub offset: f64, +} + +impl YCoordinateBounds { + pub fn symmetric_from_height(height: Option) -> Self { + YCoordinateBounds { + data_driven: height.is_none(), + height: height.unwrap_or(1.0), + offset: 0.0, + } + } +} + +#[derive(Clone, Debug, Copy)] +pub struct Bounds { + pub x_bounds: XCoordinateBounds, + pub y_bounds: YCoordinateBounds, +} + #[derive(Clone, Debug, Default)] pub enum LineType { #[default] diff --git a/rs/plotting/src/graphs/mod.rs b/rs/plotting/src/graphs/mod.rs index 5b965747..222369ee 100644 --- a/rs/plotting/src/graphs/mod.rs +++ b/rs/plotting/src/graphs/mod.rs @@ -2,3 +2,4 @@ pub mod common; pub mod packets; pub mod scalar_curve; pub mod vec3_curve; +pub mod vec3_conf_curve; diff --git a/rs/plotting/src/graphs/packets.rs b/rs/plotting/src/graphs/packets.rs index f66a1e26..97087816 100644 --- a/rs/plotting/src/graphs/packets.rs +++ b/rs/plotting/src/graphs/packets.rs @@ -1,9 +1,8 @@ - - use super::{ - common::{Color, LineType, ResetPredicate}, + common::{Bounds, Color, LineType, ResetPredicate}, scalar_curve::{NamedScalarCurve, ScalarCurve}, vec3_curve::{NamedVec3Curve, Vec3Curve}, + vec3_conf_curve::{NamedVec3ConfCurve}, }; #[derive(Clone, Debug)] @@ -11,6 +10,7 @@ pub enum PlottingPacket { /// a float value ScalarCurve(NamedScalarCurve), Vec3Curve(NamedVec3Curve), + Vec3ConfCurve(NamedVec3ConfCurve), } pub type PlottingPackets = Vec; @@ -20,6 +20,7 @@ impl PlottingPacket { color: Color, (x, y): (f64, f64), history_length: f64, + bounds: Bounds, ) -> PlottingPacket { let curve = NamedScalarCurve { plot_name: plot.into(), @@ -31,6 +32,7 @@ impl PlottingPacket { clear_x_smaller_than: ResetPredicate { clear_x_smaller_than: Some(x - history_length), }, + bounds, }, }; @@ -41,6 +43,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 +55,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_conf_curve.rs b/rs/plotting/src/graphs/vec3_conf_curve.rs new file mode 100644 index 00000000..30464318 --- /dev/null +++ b/rs/plotting/src/graphs/vec3_conf_curve.rs @@ -0,0 +1,70 @@ +use crate::graphs::common::{Color, LineType, ResetPredicate}; + +use super::common::Bounds; + +#[derive(Clone, Debug)] +pub struct Vec3ConfCurve { + pub data: Vec<(f64, (f64, f64, f64), (f64, f64, f64))>, + pub color: [Color; 3], + pub conf_color: [Color; 3], + pub curve_type: LineType, + pub clear_x_smaller_than: ResetPredicate, + pub bounds: Bounds, +} + +impl Vec3ConfCurve { + pub fn new( + data: Vec<(f64, (f64, f64, f64), (f64, f64, f64))>, + color: [Color; 3], + conf_color: [Color; 3], + curve_type: LineType, + f: ResetPredicate, + bounds: Bounds, + ) -> Self { + Vec3ConfCurve { + data, + color, + conf_color, + curve_type, + clear_x_smaller_than: f, + bounds, + } + } + + pub fn append( + &mut self, + mut pairs_as_f64: Vec<(f64, (f64, f64, f64), (f64, f64, f64))>, + color: [Color; 3], + conf_color: [Color; 3], + curve_type: LineType, + clear_x_smaller_than: ResetPredicate, + ) { + self.drain_filter(clear_x_smaller_than); + + self.data.append(&mut pairs_as_f64); + self.color = color; + self.conf_color = conf_color; + self.curve_type = curve_type; + } + + fn drain_filter(&mut self, pred: ResetPredicate) { + if pred.clear_x_smaller_than.is_none() { + return; + } + let mut i = 0; + while i < self.data.len() { + if self.data[i].0 < pred.clear_x_smaller_than.unwrap() { + self.data.remove(i); + } else { + i += 1; + } + } + } +} + +#[derive(Clone, Debug)] +pub struct NamedVec3ConfCurve { + pub plot_name: String, + pub graph_name: String, + pub scalar_curve: Vec3ConfCurve, +} 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..67afdd5a 100644 --- a/rs/plotting/src/grpc/proto.rs +++ b/rs/plotting/src/grpc/proto.rs @@ -1,12 +1,17 @@ use std::mem::size_of; -use crate::graphs::common::{LineType, ResetPredicate}; +use crate::graphs::common::{ + Bounds, LineType, ResetPredicate, XCoordinateBounds, YCoordinateBounds, +}; use crate::graphs::packets::{PlottingPacket, PlottingPackets}; use crate::graphs::scalar_curve::{NamedScalarCurve, ScalarCurve}; +use crate::graphs::vec3_conf_curve::{NamedVec3ConfCurve, Vec3ConfCurve}; use crate::graphs::vec3_curve::{NamedVec3Curve, Vec3Curve}; + use crate::grpc::farm_ng::core::plotting::proto; use crate::grpc::farm_ng::core::plotting::proto::CurveResetPredicate; use crate::grpc::proto::proto::Messages; +use crate::grpc::farm_ng::core::proto::OptionalG0Double; pub fn color_from_proto( proto: crate::grpc::farm_ng::core::proto::Color, @@ -47,6 +52,31 @@ pub fn reset_predicate_from_proto(proto: CurveResetPredicate) -> ResetPredicate } } +pub fn maybe_double_from_proto(proto: OptionalG0Double) -> Option { + if !proto.has_value { + return None; + } + Some(proto.value) +} + +pub fn bound_from_proto(proto: crate::grpc::farm_ng::core::plotting::proto::Bounds) -> Bounds { + let proto_x_bounds = proto.x_bounds.unwrap(); + let maybe_max_x = maybe_double_from_proto(proto_x_bounds.max_x.unwrap()); + let x_bounds = XCoordinateBounds { + data_driven: maybe_max_x.is_none(), + max_x: maybe_max_x.unwrap_or_default(), + len: proto_x_bounds.len, + }; + let proto_y_bounds = proto.y_bounds.unwrap(); + let maybe_height = maybe_double_from_proto(proto_y_bounds.height.unwrap()); + let y_bounds = YCoordinateBounds { + data_driven: maybe_height.is_none(), + height: maybe_height.unwrap_or(1.0), + offset: proto_y_bounds.offset, + }; + Bounds { x_bounds, y_bounds } +} + pub fn from_proto(m: Messages) -> PlottingPackets { let mut p = vec![]; for mes in m.messages { @@ -89,6 +119,7 @@ pub fn from_proto(m: Messages) -> PlottingPackets { clear_x_smaller_than: reset_predicate_from_proto( proto_curve.reset.unwrap(), ), + bounds: bound_from_proto(proto_curve.bounds.unwrap()), }, })); } @@ -143,15 +174,94 @@ pub fn from_proto(m: Messages) -> PlottingPackets { clear_x_smaller_than: reset_predicate_from_proto( proto_curve3.reset.unwrap(), ), + bounds: bound_from_proto(proto_curve3.bounds.unwrap()), }, })); } - crate::grpc::farm_ng::core::plotting::proto::message::Variant::Vec3ConfCurve(_) => { - todo!() + crate::grpc::farm_ng::core::plotting::proto::message::Variant::Vec3ConfCurve( + proto_curve3, + ) => { + let path: String = proto_curve3.path.unwrap().path_string; + let tokens = path.split('/').collect::>(); + assert_eq!( + tokens.len(), + 2, + "path must be of the form `plot_name/curve_name`, got '{}'", + path + ); + let plot_name = tokens[0]; + let curve_name = tokens[1]; + + // Convert bytes to (f64,f64) using f64::from_ne_bytes + let pairs_as_f64: Vec<(f64, (f64, f64, f64), (f64, f64, f64))> = proto_curve3 + .x_vec_conf_tuples + .unwrap() + .data + .chunks_exact(size_of::() * 7) + .map(|b| { + ( + f64::from_ne_bytes(b[0..size_of::()].try_into().unwrap()), + ( + f64::from_ne_bytes( + b[size_of::()..2 * size_of::()] + .try_into() + .unwrap(), + ), + f64::from_ne_bytes( + b[2 * size_of::()..3 * size_of::()] + .try_into() + .unwrap(), + ), + f64::from_ne_bytes( + b[3 * size_of::()..4 * size_of::()] + .try_into() + .unwrap(), + ), + ), + ( + f64::from_ne_bytes( + b[4 * size_of::()..5 * size_of::()] + .try_into() + .unwrap(), + ), + f64::from_ne_bytes( + b[5 * size_of::()..6 * size_of::()] + .try_into() + .unwrap(), + ), + f64::from_ne_bytes( + b[6 * size_of::()..7 * size_of::()] + .try_into() + .unwrap(), + ), + ), + ) + }) + .collect(); + let mut v: Vec<(f64, [f64; 3], [f64; 3])> = Vec::new(); + + for (x, y, z) in pairs_as_f64.clone() { + v.push((x, [y.0, y.1, y.2], [z.0, z.1, z.2])); + } + let cs = colors_from_proto(proto_curve3.color); + let cs_conf = colors_from_proto(proto_curve3.conf_color); + + p.push(PlottingPacket::Vec3ConfCurve(NamedVec3ConfCurve { + plot_name: plot_name.to_owned(), + graph_name: curve_name.to_owned(), + scalar_curve: Vec3ConfCurve { + data: pairs_as_f64.clone(), + color: cs, + conf_color: cs_conf, + curve_type: line_type_from_proto(proto_curve3.line_type.unwrap()), + clear_x_smaller_than: reset_predicate_from_proto( + proto_curve3.reset.unwrap(), + ), + bounds: bound_from_proto(proto_curve3.bounds.unwrap()), + }, + })); } crate::grpc::farm_ng::core::plotting::proto::message::Variant::Rects(_) => {} - crate::grpc::farm_ng::core::plotting::proto::message::Variant::XRange(_) => {} - crate::grpc::farm_ng::core::plotting::proto::message::Variant::YRange(_) => {} }, None => todo!(), } diff --git a/rs/plotting/src/plotter_gui/mod.rs b/rs/plotting/src/plotter_gui/mod.rs index eaa9d62e..a74142fe 100644 --- a/rs/plotting/src/plotter_gui/mod.rs +++ b/rs/plotting/src/plotter_gui/mod.rs @@ -1,11 +1,15 @@ +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_conf_curve::Vec3ConfCurve; 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 +17,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 { @@ -34,20 +37,23 @@ impl From for egui::Color32 { pub enum GraphType { Scalar(ScalarCurve), Vec3(Vec3Curve), + Vec3Conf(Vec3ConfCurve), } #[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 +65,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 +89,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 { @@ -99,13 +107,12 @@ impl eframe::App for PlotterGuiState { ); } GraphType::Vec3(_g) => {} + GraphType::Vec3Conf(_) => {} }) .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 +121,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 { @@ -129,13 +139,45 @@ impl eframe::App for PlotterGuiState { new_value.scalar_curve.clear_x_smaller_than.clone(), ); } + GraphType::Vec3Conf(_) => {} }) .or_insert(CurveStruct { - curve: GraphType::Vec3(new_value.scalar_curve), - toggle: true, + curve: GraphType::Vec3(new_value.scalar_curve.clone()), + show_graph: true, }); + } + + PlottingPacket::Vec3ConfCurve(new_value) => { + let plot_name = new_value.plot_name.clone(); + let curve_name = new_value.graph_name.clone(); - self.toggle_plot.insert(plot_name, true); + let plot = self.plots.entry(plot_name.clone()).or_insert(Plot { + curves: std::collections::BTreeMap::new(), + 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 { + GraphType::Scalar(_s) => {} + GraphType::Vec3(_) => {} + GraphType::Vec3Conf(g) => { + g.append( + new_value.scalar_curve.data.clone(), + new_value.scalar_curve.color, + new_value.scalar_curve.conf_color, + new_value.scalar_curve.curve_type.clone(), + new_value.scalar_curve.clear_x_smaller_than.clone(), + ); + } + }) + .or_insert(CurveStruct { + curve: GraphType::Vec3Conf(new_value.scalar_curve.clone()), + show_graph: true, + }); } } } @@ -143,9 +185,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 +207,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(); - // 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.label(format!("X-Bounds of '{}' plot", plot_name)); + + ui.horizontal_wrapped(|ui| { + ui.label("width"); + ui.add( + egui::DragValue::new(&mut plot.bounds.x_bounds.len) + .clamp_range(RangeInclusive::new(0.000001, 10000.0)), + ); + 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.max_x)); + }); + ui.checkbox(&mut plot.bounds.x_bounds.data_driven, "data-driven"); + }); + ui.separator(); + + ui.label(format!("Y-Bounds of '{}' plot", plot_name)); + + ui.horizontal_wrapped(|ui| { + ui.label("height"); + ui.add( + egui::DragValue::new(&mut plot.bounds.y_bounds.height) + .clamp_range(RangeInclusive::new(0.000001, 10000000.0)), + ); + ui.label("offset"); + ui.add( + egui::DragValue::new(&mut plot.bounds.y_bounds.offset) + .clamp_range(RangeInclusive::new(-10000000.0, 10000000.0)), + ); + 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 +279,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_max_x = -std::f64::INFINITY; + let mut data_driven_max_abs_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_max_x { + data_driven_max_x = *x; + } + + if y.abs() > data_driven_max_abs_y { + data_driven_max_abs_y = y.abs(); + } + + 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 +339,141 @@ 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_max_x { + data_driven_max_x = *x; + } + + let max_abs_y = y.0.abs().max(y.1.abs().max(y.2.abs())); + if max_abs_y > data_driven_max_abs_y { + data_driven_max_abs_y = max_abs_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)), + ); + } + } + } + } + } + GraphType::Vec3Conf(g) => { + let mut points = vec![vec![], vec![], vec![]]; + let mut up_points = vec![vec![], vec![], vec![]]; + let mut down_points = vec![vec![], vec![], vec![]]; + + for (x, y, e) in &g.data { + if x > &data_driven_max_x { + data_driven_max_x = *x; + } + + let max_abs_y = (y.0.abs() + e.0) + .max((y.1.abs() + e.1).max(y.2.abs() + e.2)); + if max_abs_y > data_driven_max_abs_y { + data_driven_max_abs_y = max_abs_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)); + + up_points[0].push(egui_plot::PlotPoint::new(*x, y.0 + e.1)); + up_points[1].push(egui_plot::PlotPoint::new(*x, y.1 + e.1)); + up_points[2].push(egui_plot::PlotPoint::new(*x, y.2 + e.2)); + + down_points[0] + .push(egui_plot::PlotPoint::new(*x, y.0 - e.0)); + down_points[1] + .push(egui_plot::PlotPoint::new(*x, y.1 - e.1)); + down_points[2] + .push(egui_plot::PlotPoint::new(*x, y.2 - e.2)); + } + + match g.curve_type { + crate::graphs::common::LineType::LineStrip => { + 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, 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 max_x = if plot_data.bounds.x_bounds.data_driven { + data_driven_max_x + } else { + plot_data.bounds.x_bounds.max_x + }; + + let height = if plot_data.bounds.y_bounds.data_driven { + 2.0 * data_driven_max_abs_y + } else { + plot_data.bounds.y_bounds.height + }; + plot_ui.set_plot_bounds(egui_plot::PlotBounds::from_min_max( + [ + max_x - plot_data.bounds.x_bounds.len, + plot_data.bounds.y_bounds.offset - 0.5 * height, + ], + [max_x, plot_data.bounds.y_bounds.offset + 0.5 * height], + )); + plot_data.bounds.x_bounds.max_x = max_x; + plot_data.bounds.y_bounds.height = height; + } }); } }); - ctx.request_repaint_after(std::time::Duration::from_secs_f64(0.1)); + ctx.request_repaint_after(std::time::Duration::from_secs_f64(0.01)); } }