From 7856dfd56d90d3f15fd0000b3392d2d579a1cbf2 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:49:21 +0100 Subject: [PATCH 01/27] Move PlotUi --- crates/egui_plot/src/lib.rs | 229 ------------------------------- crates/egui_plot/src/plot_ui.rs | 236 ++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 229 deletions(-) create mode 100644 crates/egui_plot/src/plot_ui.rs diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index ec048297193..dd1855b9467 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1345,235 +1345,6 @@ enum BoundsModification { Zoom(Vec2, PlotPoint), } -/// Provides methods to interact with a plot while building it. It is the single argument of the closure -/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. -pub struct PlotUi { - items: Vec>, - next_auto_color_idx: usize, - last_plot_transform: PlotTransform, - last_auto_bounds: Vec2b, - response: Response, - bounds_modifications: Vec, - ctx: Context, -} - -impl PlotUi { - fn auto_color(&mut self) -> Color32 { - let i = self.next_auto_color_idx; - self.next_auto_color_idx += 1; - let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 - let h = i as f32 * golden_ratio; - Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO(emilk): OkLab or some other perspective color space - } - - pub fn ctx(&self) -> &Context { - &self.ctx - } - - /// The plot bounds as they were in the last frame. If called on the first frame and the bounds were not - /// further specified in the plot builder, this will return bounds centered on the origin. The bounds do - /// not change until the plot is drawn. - pub fn plot_bounds(&self) -> PlotBounds { - *self.last_plot_transform.bounds() - } - - /// Set the plot bounds. Can be useful for implementing alternative plot navigation methods. - pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) { - self.bounds_modifications - .push(BoundsModification::Set(plot_bounds)); - } - - /// Move the plot bounds. Can be useful for implementing alternative plot navigation methods. - pub fn translate_bounds(&mut self, delta_pos: Vec2) { - self.bounds_modifications - .push(BoundsModification::Translate(delta_pos)); - } - - /// Whether the plot axes were in auto-bounds mode in the last frame. If called on the first - /// frame, this is the [`Plot`]'s default auto-bounds mode. - pub fn auto_bounds(&self) -> Vec2b { - self.last_auto_bounds - } - - /// Set the auto-bounds mode for the plot axes. - pub fn set_auto_bounds(&mut self, auto_bounds: Vec2b) { - self.bounds_modifications - .push(BoundsModification::AutoBounds(auto_bounds)); - } - - /// Can be used to check if the plot was hovered or clicked. - pub fn response(&self) -> &Response { - &self.response - } - - /// Scale the plot bounds around a position in screen coordinates. - /// - /// Can be useful for implementing alternative plot navigation methods. - /// - /// The plot bounds are divided by `zoom_factor`, therefore: - /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. - /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. - pub fn zoom_bounds(&mut self, zoom_factor: Vec2, center: PlotPoint) { - self.bounds_modifications - .push(BoundsModification::Zoom(zoom_factor, center)); - } - - /// Scale the plot bounds around the hovered position, if any. - /// - /// Can be useful for implementing alternative plot navigation methods. - /// - /// The plot bounds are divided by `zoom_factor`, therefore: - /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. - /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. - pub fn zoom_bounds_around_hovered(&mut self, zoom_factor: Vec2) { - if let Some(hover_pos) = self.pointer_coordinate() { - self.zoom_bounds(zoom_factor, hover_pos); - } - } - - /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area. - pub fn pointer_coordinate(&self) -> Option { - // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: - let last_pos = self.ctx().input(|i| i.pointer.latest_pos())? - self.response.drag_delta(); - let value = self.plot_from_screen(last_pos); - Some(value) - } - - /// The pointer drag delta in plot coordinates. - pub fn pointer_coordinate_drag_delta(&self) -> Vec2 { - let delta = self.response.drag_delta(); - let dp_dv = self.last_plot_transform.dpos_dvalue(); - Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) - } - - /// Read the transform between plot coordinates and screen coordinates. - pub fn transform(&self) -> &PlotTransform { - &self.last_plot_transform - } - - /// Transform the plot coordinates to screen coordinates. - pub fn screen_from_plot(&self, position: PlotPoint) -> Pos2 { - self.last_plot_transform.position_from_point(&position) - } - - /// Transform the screen coordinates to plot coordinates. - pub fn plot_from_screen(&self, position: Pos2) -> PlotPoint { - self.last_plot_transform.value_from_position(position) - } - - /// Add a data line. - pub fn line(&mut self, mut line: Line) { - if line.series.is_empty() { - return; - }; - - // Give the stroke an automatic color if no color has been assigned. - if line.stroke.color == Color32::TRANSPARENT { - line.stroke.color = self.auto_color(); - } - self.items.push(Box::new(line)); - } - - /// Add a polygon. The polygon has to be convex. - pub fn polygon(&mut self, mut polygon: Polygon) { - if polygon.series.is_empty() { - return; - }; - - // Give the stroke an automatic color if no color has been assigned. - if polygon.stroke.color == Color32::TRANSPARENT { - polygon.stroke.color = self.auto_color(); - } - self.items.push(Box::new(polygon)); - } - - /// Add a text. - pub fn text(&mut self, text: Text) { - if text.text.is_empty() { - return; - }; - - self.items.push(Box::new(text)); - } - - /// Add data points. - pub fn points(&mut self, mut points: Points) { - if points.series.is_empty() { - return; - }; - - // Give the points an automatic color if no color has been assigned. - if points.color == Color32::TRANSPARENT { - points.color = self.auto_color(); - } - self.items.push(Box::new(points)); - } - - /// Add arrows. - pub fn arrows(&mut self, mut arrows: Arrows) { - if arrows.origins.is_empty() || arrows.tips.is_empty() { - return; - }; - - // Give the arrows an automatic color if no color has been assigned. - if arrows.color == Color32::TRANSPARENT { - arrows.color = self.auto_color(); - } - self.items.push(Box::new(arrows)); - } - - /// Add an image. - pub fn image(&mut self, image: PlotImage) { - self.items.push(Box::new(image)); - } - - /// Add a horizontal line. - /// Can be useful e.g. to show min/max bounds or similar. - /// Always fills the full width of the plot. - pub fn hline(&mut self, mut hline: HLine) { - if hline.stroke.color == Color32::TRANSPARENT { - hline.stroke.color = self.auto_color(); - } - self.items.push(Box::new(hline)); - } - - /// Add a vertical line. - /// Can be useful e.g. to show min/max bounds or similar. - /// Always fills the full height of the plot. - pub fn vline(&mut self, mut vline: VLine) { - if vline.stroke.color == Color32::TRANSPARENT { - vline.stroke.color = self.auto_color(); - } - self.items.push(Box::new(vline)); - } - - /// Add a box plot diagram. - pub fn box_plot(&mut self, mut box_plot: BoxPlot) { - if box_plot.boxes.is_empty() { - return; - } - - // Give the elements an automatic color if no color has been assigned. - if box_plot.default_color == Color32::TRANSPARENT { - box_plot = box_plot.color(self.auto_color()); - } - self.items.push(Box::new(box_plot)); - } - - /// Add a bar chart. - pub fn bar_chart(&mut self, mut chart: BarChart) { - if chart.bars.is_empty() { - return; - } - - // Give the elements an automatic color if no color has been assigned. - if chart.default_color == Color32::TRANSPARENT { - chart = chart.color(self.auto_color()); - } - self.items.push(Box::new(chart)); - } -} - // ---------------------------------------------------------------------------- // Grid diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs new file mode 100644 index 00000000000..31879dc854e --- /dev/null +++ b/crates/egui_plot/src/plot_ui.rs @@ -0,0 +1,236 @@ +use egui::{epaint::Hsva, Color32, Context, Pos2, Response, Vec2, Vec2b}; + +use crate::{ + items::PlotItem, + Arrows, BarChart, BoundsModification, BoxPlot, PlotPoint, HLine, Line, PlotBounds, PlotImage, + PlotTransform, Points, Polygon, Text, VLine, +}; + +/// Provides methods to interact with a plot while building it. It is the single argument of the closure +/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. +pub struct PlotUi { + items: Vec>, + next_auto_color_idx: usize, + last_plot_transform: PlotTransform, + last_auto_bounds: Vec2b, + response: Response, + bounds_modifications: Vec, + ctx: Context, +} + +impl PlotUi { + fn auto_color(&mut self) -> Color32 { + let i = self.next_auto_color_idx; + self.next_auto_color_idx += 1; + let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 + let h = i as f32 * golden_ratio; + Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO(emilk): OkLab or some other perspective color space + } + + pub fn ctx(&self) -> &Context { + &self.ctx + } + + /// The plot bounds as they were in the last frame. If called on the first frame and the bounds were not + /// further specified in the plot builder, this will return bounds centered on the origin. The bounds do + /// not change until the plot is drawn. + pub fn plot_bounds(&self) -> PlotBounds { + *self.last_plot_transform.bounds() + } + + /// Set the plot bounds. Can be useful for implementing alternative plot navigation methods. + pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) { + self.bounds_modifications + .push(BoundsModification::Set(plot_bounds)); + } + + /// Move the plot bounds. Can be useful for implementing alternative plot navigation methods. + pub fn translate_bounds(&mut self, delta_pos: Vec2) { + self.bounds_modifications + .push(BoundsModification::Translate(delta_pos)); + } + + /// Whether the plot axes were in auto-bounds mode in the last frame. If called on the first + /// frame, this is the [`Plot`]'s default auto-bounds mode. + pub fn auto_bounds(&self) -> Vec2b { + self.last_auto_bounds + } + + /// Set the auto-bounds mode for the plot axes. + pub fn set_auto_bounds(&mut self, auto_bounds: Vec2b) { + self.bounds_modifications + .push(BoundsModification::AutoBounds(auto_bounds)); + } + + /// Can be used to check if the plot was hovered or clicked. + pub fn response(&self) -> &Response { + &self.response + } + + /// Scale the plot bounds around a position in screen coordinates. + /// + /// Can be useful for implementing alternative plot navigation methods. + /// + /// The plot bounds are divided by `zoom_factor`, therefore: + /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. + /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. + pub fn zoom_bounds(&mut self, zoom_factor: Vec2, center: PlotPoint) { + self.bounds_modifications + .push(BoundsModification::Zoom(zoom_factor, center)); + } + + /// Scale the plot bounds around the hovered position, if any. + /// + /// Can be useful for implementing alternative plot navigation methods. + /// + /// The plot bounds are divided by `zoom_factor`, therefore: + /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. + /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. + pub fn zoom_bounds_around_hovered(&mut self, zoom_factor: Vec2) { + if let Some(hover_pos) = self.pointer_coordinate() { + self.zoom_bounds(zoom_factor, hover_pos); + } + } + + /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area. + pub fn pointer_coordinate(&self) -> Option { + // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: + let last_pos = self.ctx().input(|i| i.pointer.latest_pos())? - self.response.drag_delta(); + let value = self.plot_from_screen(last_pos); + Some(value) + } + + /// The pointer drag delta in plot coordinates. + pub fn pointer_coordinate_drag_delta(&self) -> Vec2 { + let delta = self.response.drag_delta(); + let dp_dv = self.last_plot_transform.dpos_dvalue(); + Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) + } + + /// Read the transform between plot coordinates and screen coordinates. + pub fn transform(&self) -> &PlotTransform { + &self.last_plot_transform + } + + /// Transform the plot coordinates to screen coordinates. + pub fn screen_from_plot(&self, position: PlotPoint) -> Pos2 { + self.last_plot_transform.position_from_point(&position) + } + + /// Transform the screen coordinates to plot coordinates. + pub fn plot_from_screen(&self, position: Pos2) -> PlotPoint { + self.last_plot_transform.value_from_position(position) + } + + /// Add a data line. + pub fn line(&mut self, mut line: Line) { + if line.series.is_empty() { + return; + }; + + // Give the stroke an automatic color if no color has been assigned. + if line.stroke.color == Color32::TRANSPARENT { + line.stroke.color = self.auto_color(); + } + self.items.push(Box::new(line)); + } + + /// Add a polygon. The polygon has to be convex. + pub fn polygon(&mut self, mut polygon: Polygon) { + if polygon.series.is_empty() { + return; + }; + + // Give the stroke an automatic color if no color has been assigned. + if polygon.stroke.color == Color32::TRANSPARENT { + polygon.stroke.color = self.auto_color(); + } + self.items.push(Box::new(polygon)); + } + + /// Add a text. + pub fn text(&mut self, text: Text) { + if text.text.is_empty() { + return; + }; + + self.items.push(Box::new(text)); + } + + /// Add data points. + pub fn points(&mut self, mut points: Points) { + if points.series.is_empty() { + return; + }; + + // Give the points an automatic color if no color has been assigned. + if points.color == Color32::TRANSPARENT { + points.color = self.auto_color(); + } + self.items.push(Box::new(points)); + } + + /// Add arrows. + pub fn arrows(&mut self, mut arrows: Arrows) { + if arrows.origins.is_empty() || arrows.tips.is_empty() { + return; + }; + + // Give the arrows an automatic color if no color has been assigned. + if arrows.color == Color32::TRANSPARENT { + arrows.color = self.auto_color(); + } + self.items.push(Box::new(arrows)); + } + + /// Add an image. + pub fn image(&mut self, image: PlotImage) { + self.items.push(Box::new(image)); + } + + /// Add a horizontal line. + /// Can be useful e.g. to show min/max bounds or similar. + /// Always fills the full width of the plot. + pub fn hline(&mut self, mut hline: HLine) { + if hline.stroke.color == Color32::TRANSPARENT { + hline.stroke.color = self.auto_color(); + } + self.items.push(Box::new(hline)); + } + + /// Add a vertical line. + /// Can be useful e.g. to show min/max bounds or similar. + /// Always fills the full height of the plot. + pub fn vline(&mut self, mut vline: VLine) { + if vline.stroke.color == Color32::TRANSPARENT { + vline.stroke.color = self.auto_color(); + } + self.items.push(Box::new(vline)); + } + + /// Add a box plot diagram. + pub fn box_plot(&mut self, mut box_plot: BoxPlot) { + if box_plot.boxes.is_empty() { + return; + } + + // Give the elements an automatic color if no color has been assigned. + if box_plot.default_color == Color32::TRANSPARENT { + box_plot = box_plot.color(self.auto_color()); + } + self.items.push(Box::new(box_plot)); + } + + /// Add a bar chart. + pub fn bar_chart(&mut self, mut chart: BarChart) { + if chart.bars.is_empty() { + return; + } + + // Give the elements an automatic color if no color has been assigned. + if chart.default_color == Color32::TRANSPARENT { + chart = chart.color(self.auto_color()); + } + self.items.push(Box::new(chart)); + } +} \ No newline at end of file From 292ef19505a26014aff212e3b08fcfab4d03542a Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:53:39 +0100 Subject: [PATCH 02/27] Add GenericPlotPoints trait and impl for iterator --- crates/egui_plot/src/items/mod.rs | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 4c2eb77a92e..5be32159b19 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -112,6 +112,42 @@ pub(super) trait PlotItem { // ---------------------------------------------------------------------------- +/// Trait shared by structs that are plot points. +/// This abstracts behaviour away from owned, generated and borrowed plot points + +pub trait GenericPlotPoints { + type Item; + type Iter: Iterator + Clone; + + fn points(&self) -> Self::Iter; + + fn bounds(&self) -> PlotBounds; + + fn initialize(&mut self, _: RangeInclusive) {} +} + +impl<'a, T> GenericPlotPoints for T +where + T: Iterator + Clone, +{ + type Item = &'a PlotPoint; + type Iter = Self; + + fn points(&self) -> Self::Iter { + self.clone() + } + + fn bounds(&self) -> PlotBounds { + let mut bounds = PlotBounds::NOTHING; + for point in self.clone() { + bounds.extend_with(point); + } + bounds + } +} + +// ---------------------------------------------------------------------------- + /// A horizontal line in a plot, filling the full width #[derive(Clone, Debug, PartialEq)] pub struct HLine { From 8cffd4599e5861ea1c0601c12d1156590d2a5297 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:01:39 +0100 Subject: [PATCH 03/27] Add PlotUiBuilder This type is used to avoid lifetime issues with buil_fn in lib.rs --- crates/egui_plot/src/plot_ui.rs | 57 ++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs index 31879dc854e..ca27ee2df36 100644 --- a/crates/egui_plot/src/plot_ui.rs +++ b/crates/egui_plot/src/plot_ui.rs @@ -6,6 +6,61 @@ use crate::{ PlotTransform, Points, Polygon, Text, VLine, }; +pub struct PlotUiBuilder { + pub(crate) next_auto_color_idx: usize, + pub(crate) last_plot_transform: PlotTransform, + pub(crate) last_auto_bounds: Vec2b, + pub(crate) response: Response, + pub(crate) bounds_modifications: Vec, + pub(crate) ctx: Context, +} + +impl PlotUiBuilder { + pub fn into_plot_ui<'a>(self) -> PlotUi<'a> { + let Self { + next_auto_color_idx, + last_plot_transform, + last_auto_bounds, + response, + bounds_modifications, + ctx, + } = self; + + PlotUi { + items: Vec::new(), + next_auto_color_idx, + last_plot_transform, + last_auto_bounds, + response, + bounds_modifications, + ctx, + } + } +} + +impl From for PlotUi<'_> { + fn from(value: PlotUiBuilder) -> Self { + let PlotUiBuilder { + next_auto_color_idx, + last_plot_transform, + last_auto_bounds, + response, + bounds_modifications, + ctx, + } = value; + + PlotUi { + items: Vec::new(), + next_auto_color_idx, + last_plot_transform, + last_auto_bounds, + response, + bounds_modifications, + ctx, + } + } +} + /// Provides methods to interact with a plot while building it. It is the single argument of the closure /// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. pub struct PlotUi { @@ -233,4 +288,4 @@ impl PlotUi { } self.items.push(Box::new(chart)); } -} \ No newline at end of file +} From 9baf13f111c747da632729e958037937c7a83281 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:04:39 +0100 Subject: [PATCH 04/27] Add lifetime to plotui for boxed PlotItem --- crates/egui_plot/src/plot_ui.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs index ca27ee2df36..641b9508e95 100644 --- a/crates/egui_plot/src/plot_ui.rs +++ b/crates/egui_plot/src/plot_ui.rs @@ -63,17 +63,17 @@ impl From for PlotUi<'_> { /// Provides methods to interact with a plot while building it. It is the single argument of the closure /// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. -pub struct PlotUi { - items: Vec>, - next_auto_color_idx: usize, - last_plot_transform: PlotTransform, - last_auto_bounds: Vec2b, - response: Response, - bounds_modifications: Vec, - ctx: Context, +pub struct PlotUi<'a> { + pub(crate) items: Vec>, + pub(crate) next_auto_color_idx: usize, + pub(crate) last_plot_transform: PlotTransform, + pub(crate) last_auto_bounds: Vec2b, + pub(crate) response: Response, + pub(crate) bounds_modifications: Vec, + pub(crate) ctx: Context, } -impl PlotUi { +impl<'a> PlotUi<'a> { fn auto_color(&mut self) -> Color32 { let i = self.next_auto_color_idx; self.next_auto_color_idx += 1; From 914ff8261a203f2dcbbb8ad98c4bf04f69320293 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:22:38 +0100 Subject: [PATCH 05/27] Make Points generic --- crates/egui_plot/src/items/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 5be32159b19..5fdcf404cd3 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -809,8 +809,8 @@ impl PlotItem for Text { } /// A set of points. -pub struct Points { - pub(super) series: PlotPoints, +pub struct Points { + pub(super) series: T, pub(super) shape: MarkerShape, @@ -830,7 +830,7 @@ pub struct Points { pub(super) stems: Option, } -impl Points { +impl Points pub fn new(series: impl Into) -> Self { Self { series: series.into(), From 8a44bd68766d0b11256a3c88d2cbc64c56428872 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:26:17 +0100 Subject: [PATCH 06/27] Add generic constructor for Points --- crates/egui_plot/src/items/mod.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 5fdcf404cd3..dc2fb11c5d6 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -831,6 +831,25 @@ pub struct Points { } impl Points +where + T: GenericPlotPoints, +{ + pub fn new_generic(t: T) -> Self { + Self { + series: t, + shape: MarkerShape::Circle, + color: Color32::TRANSPARENT, + filled: true, + radius: 1.0, + name: Default::default(), + highlight: false, + stems: None, + } + } +} + + +impl Points { pub fn new(series: impl Into) -> Self { Self { series: series.into(), From e82d83229de6069c3f0272ffee33ec398da64e2e Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:28:22 +0100 Subject: [PATCH 07/27] Add functions to mutate Points in place This avoids requiring move and set --- crates/egui_plot/src/items/mod.rs | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index dc2fb11c5d6..9433bdede54 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -862,7 +862,9 @@ impl Points { stems: None, } } +} +impl Points { /// Set the shape of the markers. #[inline] pub fn shape(mut self, shape: MarkerShape) -> Self { @@ -870,6 +872,12 @@ impl Points { self } + /// Set the shape of the markers. + #[inline] + pub fn shape_mut(&mut self, shape: MarkerShape) { + self.shape = shape; + } + /// Highlight these points in the plot by scaling up their markers. #[inline] pub fn highlight(mut self, highlight: bool) -> Self { @@ -877,6 +885,12 @@ impl Points { self } + /// Highlight these points in the plot by scaling up their markers. + #[inline] + pub fn highlight_mut(&mut self, highlight: bool) { + self.highlight = highlight; + } + /// Set the marker's color. #[inline] pub fn color(mut self, color: impl Into) -> Self { @@ -884,6 +898,12 @@ impl Points { self } + /// Set the marker's color. + #[inline] + pub fn color_mut(&mut self, color: impl Into) { + self.color = color.into(); + } + /// Whether to fill the marker. #[inline] pub fn filled(mut self, filled: bool) -> Self { @@ -891,6 +911,12 @@ impl Points { self } + /// Whether to fill the marker. + #[inline] + pub fn filled_mut(&mut self, filled: bool) { + self.filled = filled; + } + /// Whether to add stems between the markers and a horizontal reference line. #[inline] pub fn stems(mut self, y_reference: impl Into) -> Self { @@ -898,6 +924,12 @@ impl Points { self } + /// Whether to add stems between the markers and a horizontal reference line. + #[inline] + pub fn stems_mut(&mut self, y_reference: impl Into) { + self.stems = Some(y_reference.into()); + } + /// Set the maximum extent of the marker around its position. #[inline] pub fn radius(mut self, radius: impl Into) -> Self { @@ -905,6 +937,12 @@ impl Points { self } + /// Set the maximum extent of the marker around its position. + #[inline] + pub fn radius_mut(&mut self, radius: impl Into) { + self.radius = radius.into(); + } + /// Name of this set of points. /// /// This name will show up in the plot legend, if legends are turned on. @@ -919,7 +957,7 @@ impl Points { } } -impl PlotItem for Points { +impl PlotItem for Points { fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let sqrt_3 = 3_f32.sqrt(); let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0; From d9c46111aed3b8542175aa2394a1d215b4d5f20b Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:29:50 +0100 Subject: [PATCH 08/27] Add PlotItem impl for generic Points and PlotGeometry::GenericPoints PlotGeometry::GenericPoints has same behaviour as PlotGeometry::Rects --- crates/egui_plot/src/items/mod.rs | 211 +++++++++++++++++++++++++++ crates/egui_plot/src/items/values.rs | 5 + 2 files changed, 216 insertions(+) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 9433bdede54..1fb984e3cf1 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -66,6 +66,11 @@ pub(super) trait PlotItem { PlotGeometry::Rects => { panic!("If the PlotItem is made of rects, it should implement find_closest()") } + PlotGeometry::GenericPoints => { + panic!( + "If the PlotItem is made of Generic points, it should implement find_closest()" + ) + } } } @@ -85,6 +90,9 @@ pub(super) trait PlotItem { PlotGeometry::Rects => { panic!("If the PlotItem is made of rects, it should implement on_hover()") } + PlotGeometry::GenericPoints => { + panic!("If the PlotItem is made of generic points, it should implement on_hover()") + } }; let line_color = if plot.ui.visuals().dark_mode { @@ -1115,6 +1123,209 @@ impl PlotItem for Points { } } +impl<'a, T> PlotItem for Points +where + T: GenericPlotPoints, +{ + fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + let sqrt_3 = 3_f32.sqrt(); + let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0; + let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt(); + + let Self { + series, + shape, + color, + filled, + mut radius, + highlight, + stems, + .. + } = self; + + let stroke_size = radius / 5.0; + + let default_stroke = Stroke::new(stroke_size, *color); + let mut stem_stroke = default_stroke; + let (fill, stroke) = if *filled { + (*color, Stroke::NONE) + } else { + (Color32::TRANSPARENT, default_stroke) + }; + + if *highlight { + radius *= 2f32.sqrt(); + stem_stroke.width *= 2.0; + } + + let y_reference = stems.map(|y| transform.position_from_point(&PlotPoint::new(0.0, y)).y); + + series + .points() + .map(|value| transform.position_from_point(value)) + .for_each(|center| { + let tf = |dx: f32, dy: f32| -> Pos2 { center + radius * vec2(dx, dy) }; + + if let Some(y) = y_reference { + let stem = Shape::line_segment([center, pos2(center.x, y)], stem_stroke); + shapes.push(stem); + } + + match shape { + MarkerShape::Circle => { + shapes.push(Shape::Circle(epaint::CircleShape { + center, + radius, + fill, + stroke, + })); + } + MarkerShape::Diamond => { + let points = vec![ + tf(0.0, 1.0), // bottom + tf(-1.0, 0.0), // left + tf(0.0, -1.0), // top + tf(1.0, 0.0), // right + ]; + shapes.push(Shape::convex_polygon(points, fill, stroke)); + } + MarkerShape::Square => { + let points = vec![ + tf(-frac_1_sqrt_2, frac_1_sqrt_2), + tf(-frac_1_sqrt_2, -frac_1_sqrt_2), + tf(frac_1_sqrt_2, -frac_1_sqrt_2), + tf(frac_1_sqrt_2, frac_1_sqrt_2), + ]; + shapes.push(Shape::convex_polygon(points, fill, stroke)); + } + MarkerShape::Cross => { + let diagonal1 = [ + tf(-frac_1_sqrt_2, -frac_1_sqrt_2), + tf(frac_1_sqrt_2, frac_1_sqrt_2), + ]; + let diagonal2 = [ + tf(frac_1_sqrt_2, -frac_1_sqrt_2), + tf(-frac_1_sqrt_2, frac_1_sqrt_2), + ]; + shapes.push(Shape::line_segment(diagonal1, default_stroke)); + shapes.push(Shape::line_segment(diagonal2, default_stroke)); + } + MarkerShape::Plus => { + let horizontal = [tf(-1.0, 0.0), tf(1.0, 0.0)]; + let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)]; + shapes.push(Shape::line_segment(horizontal, default_stroke)); + shapes.push(Shape::line_segment(vertical, default_stroke)); + } + MarkerShape::Up => { + let points = + vec![tf(0.0, -1.0), tf(0.5 * sqrt_3, 0.5), tf(-0.5 * sqrt_3, 0.5)]; + shapes.push(Shape::convex_polygon(points, fill, stroke)); + } + MarkerShape::Down => { + let points = vec![ + tf(0.0, 1.0), + tf(-0.5 * sqrt_3, -0.5), + tf(0.5 * sqrt_3, -0.5), + ]; + shapes.push(Shape::convex_polygon(points, fill, stroke)); + } + MarkerShape::Left => { + let points = + vec![tf(-1.0, 0.0), tf(0.5, -0.5 * sqrt_3), tf(0.5, 0.5 * sqrt_3)]; + shapes.push(Shape::convex_polygon(points, fill, stroke)); + } + MarkerShape::Right => { + let points = vec![ + tf(1.0, 0.0), + tf(-0.5, 0.5 * sqrt_3), + tf(-0.5, -0.5 * sqrt_3), + ]; + shapes.push(Shape::convex_polygon(points, fill, stroke)); + } + MarkerShape::Asterisk => { + let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)]; + let diagonal1 = [tf(-frac_sqrt_3_2, 0.5), tf(frac_sqrt_3_2, -0.5)]; + let diagonal2 = [tf(-frac_sqrt_3_2, -0.5), tf(frac_sqrt_3_2, 0.5)]; + shapes.push(Shape::line_segment(vertical, default_stroke)); + shapes.push(Shape::line_segment(diagonal1, default_stroke)); + shapes.push(Shape::line_segment(diagonal2, default_stroke)); + } + } + }); + } + + fn initialize(&mut self, x_range: RangeInclusive) { + self.series.initialize(x_range); + } + + fn name(&self) -> &str { + self.name.as_str() + } + + fn color(&self) -> Color32 { + self.color + } + + fn highlight(&mut self) { + self.highlight = true; + } + + fn highlighted(&self) -> bool { + self.highlight + } + + fn geometry(&self) -> PlotGeometry<'_> { + PlotGeometry::GenericPoints + } + + fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option { + self.series + .points() + .enumerate() + .map(|(index, value)| { + let pos = transform.position_from_point(value); + let dist_sq = point.distance_sq(pos); + ClosestElem { index, dist_sq } + }) + .min_by_key(|e| e.dist_sq.ord()) + } + + fn on_hover( + &self, + elem: ClosestElem, + shapes: &mut Vec, + cursors: &mut Vec, + plot: &PlotConfig<'_>, + label_formatter: &LabelFormatter, + ) { + let line_color = if plot.ui.visuals().dark_mode { + Color32::from_gray(100).additive() + } else { + Color32::from_black_alpha(180) + }; + + // this method is only called, if the value is in the result set of find_closest() + if let Some(value) = self.series.points().nth(elem.index) { + let pointer = plot.transform.position_from_point(value); + shapes.push(Shape::circle_filled(pointer, 3.0, line_color)); + + rulers_at_value( + pointer, + *value, + self.name(), + plot, + shapes, + cursors, + label_formatter, + ); + } + } + + fn bounds(&self) -> PlotBounds { + self.series.bounds() + } +} + /// A set of arrows. pub struct Arrows { pub(super) origins: PlotPoints, diff --git a/crates/egui_plot/src/items/values.rs b/crates/egui_plot/src/items/values.rs index d05fdab36e7..1d7953302fc 100644 --- a/crates/egui_plot/src/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -368,6 +368,11 @@ pub(crate) enum PlotGeometry<'a> { // Has currently no data, as it would require copying rects or iterating a list of pointers. // Instead, geometry-based functions are directly implemented in the respective PlotItem impl. Rects, + + /// Generic points + // Has no data, as without additional info it is not possible to efficiently index into the generic data. + // Instead, geometry-based functions are directly implemented in the respective PlotItem impl. + GenericPoints, } // ---------------------------------------------------------------------------- From 5e9b705dfcfbdedfcbe816b89a7d62d63547ebd7 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:35:14 +0100 Subject: [PATCH 09/27] Remove enum instance Borrowed from PlotPoints --- crates/egui_plot/src/items/values.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/egui_plot/src/items/values.rs b/crates/egui_plot/src/items/values.rs index 1d7953302fc..c170b4137a5 100644 --- a/crates/egui_plot/src/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -156,7 +156,6 @@ impl Default for Orientation { pub enum PlotPoints { Owned(Vec), Generator(ExplicitGenerator), - // Borrowed(&[PlotPoint]), // TODO: Lifetimes are tricky in this case. } impl Default for PlotPoints { From 842866381e921a84e581c6767b95394172995fb2 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:48:09 +0100 Subject: [PATCH 10/27] Make legend take an iterator Legend used to take a slice but this does not work if you have an iterator but slices can be iterators. It is thus best to take a simple iterator of the desired type. --- crates/egui_plot/src/legend.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/egui_plot/src/legend.rs b/crates/egui_plot/src/legend.rs index 339c5d28dbe..e1c81d11f1b 100644 --- a/crates/egui_plot/src/legend.rs +++ b/crates/egui_plot/src/legend.rs @@ -179,12 +179,16 @@ pub(super) struct LegendWidget { impl LegendWidget { /// Create a new legend from items, the names of items that are hidden and the style of the /// text. Returns `None` if the legend has no entries. - pub(super) fn try_new( + pub(super) fn try_new<'a, 'b, I>( rect: Rect, config: Legend, - items: &[Box], + items: I, hidden_items: &ahash::HashSet, // Existing hiddent items in the plot memory. - ) -> Option { + ) -> Option + where + 'b: 'a, + I: Iterator>, + { // If `config.hidden_items` is not `None`, it is used. let hidden_items = config.hidden_items.as_ref().unwrap_or(hidden_items); @@ -192,7 +196,7 @@ impl LegendWidget { // checkbox. If their colors don't match, we pick a neutral color for the checkbox. let mut entries: BTreeMap = BTreeMap::new(); items - .iter() + .into_iter() .filter(|item| !item.name().is_empty()) .for_each(|item| { entries From 7ea8a50a2aa84b51a9f9a068126a95472b068849 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:49:00 +0100 Subject: [PATCH 11/27] Add module plot_ui and import PlotUi and PlotUiBuilder --- crates/egui_plot/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index dd1855b9467..94da4723f5d 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -8,7 +8,6 @@ use std::{ops::RangeInclusive, sync::Arc}; use egui::ahash::HashMap; use epaint::util::FloatOrd; -use epaint::Hsva; use axis::AxisWidget; use items::PlotItem; @@ -21,6 +20,7 @@ pub use items::{ Orientation, PlotImage, PlotPoint, PlotPoints, Points, Polygon, Text, VLine, }; pub use legend::{Corner, Legend}; +use plot_ui::{PlotUi, PlotUiBuilder}; pub use transform::{PlotBounds, PlotTransform}; use items::{horizontal_line, rulers_color, vertical_line}; @@ -30,6 +30,7 @@ pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}; mod axis; mod items; mod legend; +mod plot_ui; mod transform; type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String; From 4d58b4aa4ddaa7dfe30871c3761de2532a711181 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:51:14 +0100 Subject: [PATCH 12/27] Adjust new show function to have appropriate type - Remove old show that was just wrapper of show_dyn - Rename show_dyn to show - Change type of show to match old show and new types --- crates/egui_plot/src/lib.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 94da4723f5d..eb47da91d2f 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -706,15 +706,11 @@ impl Plot { } /// Interact with and add items to the plot and finally draw it. - pub fn show(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> PlotResponse { - self.show_dyn(ui, Box::new(build_fn)) - } - fn show_dyn<'a, R>( - self, - ui: &mut Ui, - build_fn: Box R + 'a>, - ) -> PlotResponse { + pub fn show<'a, F, R>(self, ui: &mut Ui, build_fn: F) -> PlotResponse + where + F: FnOnce(PlotUiBuilder) -> (R, PlotUi<'a>), + { let Self { id_source, center_axis, From e9cc2b6e4b9ea0f944626cc52b0a639202516add Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:53:36 +0100 Subject: [PATCH 13/27] Adjust show body to construct PlotUiBuilder for build_fn - buil_fn now takes PlotUiBuilder and returns PlotUi and R --- crates/egui_plot/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index eb47da91d2f..d61c78d3b2d 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -882,8 +882,7 @@ impl Plot { } = memory; // Call the plot build function. - let mut plot_ui = PlotUi { - items: Vec::new(), + let builder = PlotUiBuilder { next_auto_color_idx: 0, last_plot_transform, last_auto_bounds: auto_bounds, @@ -891,7 +890,9 @@ impl Plot { bounds_modifications: Vec::new(), ctx: ui.ctx().clone(), }; - let inner = build_fn(&mut plot_ui); + + let (inner, plot_ui) = build_fn(builder); + let PlotUi { mut items, mut response, From cfc4ab0916f55eededc148dce44f0f71e19ede43 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:54:05 +0100 Subject: [PATCH 14/27] Adjust legend call to updated legend signature of Iterator --- crates/egui_plot/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index d61c78d3b2d..3456f364521 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -915,7 +915,8 @@ impl Plot { // --- Legend --- let legend = legend_config - .and_then(|config| LegendWidget::try_new(rect, config, &items, &hidden_items)); + .and_then(|config| LegendWidget::try_new(rect, config, items.iter(), &hidden_items)); + // Don't show hover cursor when hovering over legend. if hovered_entry.is_some() { show_x = false; From e1cbec1a6a85445665c0c693c4132b3036ce6f7f Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:55:12 +0100 Subject: [PATCH 15/27] Adjust PlotUiPrepared to take lifetime --- crates/egui_plot/src/lib.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 3456f364521..3d72c3f7573 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1419,8 +1419,8 @@ pub fn uniform_grid_spacer(spacer: impl Fn(GridInput) -> [f64; 3] + 'static) -> // ---------------------------------------------------------------------------- -struct PreparedPlot { - items: Vec>, +struct PreparedPlot<'a> { + items: Vec>, show_x: bool, show_y: bool, label_formatter: LabelFormatter, @@ -1437,8 +1437,10 @@ struct PreparedPlot { clamp_grid: bool, } -impl PreparedPlot { - fn ui(self, ui: &mut Ui, response: &Response) -> Vec { +impl<'a> PreparedPlot<'a> { + fn ui(&self, ui: &mut Ui, response: &Response) -> Vec { + let items = &self.items; + let mut axes_shapes = Vec::new(); if self.show_grid.x { @@ -1451,13 +1453,14 @@ impl PreparedPlot { // Sort the axes by strength so that those with higher strength are drawn in front. axes_shapes.sort_by(|(_, strength1), (_, strength2)| strength1.total_cmp(strength2)); - let mut shapes = axes_shapes.into_iter().map(|(shape, _)| shape).collect(); + let mut shapes: Vec<_> = axes_shapes.into_iter().map(|(shape, _)| shape).collect(); let transform = &self.transform; let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default()); plot_ui.set_clip_rect(*transform.frame()); - for item in &self.items { + + for item in items { item.shapes(&mut plot_ui, transform, &mut shapes); } From e57ef931a1642e88872638ae6354f126f5a1cb22 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:55:59 +0100 Subject: [PATCH 16/27] Adjust doc example to match new build_fn in show function --- crates/egui_plot/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 3d72c3f7573..56913cc063d 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -172,7 +172,12 @@ pub struct PlotResponse { /// [x, x.sin()] /// }).collect(); /// let line = Line::new(sin); -/// Plot::new("my_plot").view_aspect(2.0).show(ui, |plot_ui| plot_ui.line(line)); +/// Plot::new("my_plot").view_aspect(2.0).show(ui, |builder| { +/// let mut plot_ui = builder.into_plot_ui(); +/// plot_ui.line(line); +/// +/// ((), plot_ui) +/// }); /// # }); /// ``` pub struct Plot { From f450c6b30ebf0443e175b6c00679c9b3b6ce537d Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:02:04 +0100 Subject: [PATCH 17/27] Adjust PlotUi builder style and add borrowed points function - Return a ref mut self on return which allows chaining of multiple functions in a nicer way then before. - Add function to add Generic plot points to items --- crates/egui_plot/src/plot_ui.rs | 91 +++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 27 deletions(-) diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs index 641b9508e95..9d1994b61b6 100644 --- a/crates/egui_plot/src/plot_ui.rs +++ b/crates/egui_plot/src/plot_ui.rs @@ -1,9 +1,9 @@ use egui::{epaint::Hsva, Color32, Context, Pos2, Response, Vec2, Vec2b}; use crate::{ - items::PlotItem, + items::{GenericPlotPoints, PlotItem}, Arrows, BarChart, BoundsModification, BoxPlot, PlotPoint, HLine, Line, PlotBounds, PlotImage, - PlotTransform, Points, Polygon, Text, VLine, + PlotPoints, PlotTransform, Points, Polygon, Text, VLine, }; pub struct PlotUiBuilder { @@ -94,15 +94,19 @@ impl<'a> PlotUi<'a> { } /// Set the plot bounds. Can be useful for implementing alternative plot navigation methods. - pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) { + pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) -> &mut Self { self.bounds_modifications .push(BoundsModification::Set(plot_bounds)); + + self } /// Move the plot bounds. Can be useful for implementing alternative plot navigation methods. - pub fn translate_bounds(&mut self, delta_pos: Vec2) { + pub fn translate_bounds(&mut self, delta_pos: Vec2) -> &mut Self { self.bounds_modifications .push(BoundsModification::Translate(delta_pos)); + + self } /// Whether the plot axes were in auto-bounds mode in the last frame. If called on the first @@ -112,9 +116,11 @@ impl<'a> PlotUi<'a> { } /// Set the auto-bounds mode for the plot axes. - pub fn set_auto_bounds(&mut self, auto_bounds: Vec2b) { + pub fn set_auto_bounds(&mut self, auto_bounds: Vec2b) -> &mut Self { self.bounds_modifications .push(BoundsModification::AutoBounds(auto_bounds)); + + self } /// Can be used to check if the plot was hovered or clicked. @@ -129,9 +135,11 @@ impl<'a> PlotUi<'a> { /// The plot bounds are divided by `zoom_factor`, therefore: /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. - pub fn zoom_bounds(&mut self, zoom_factor: Vec2, center: PlotPoint) { + pub fn zoom_bounds(&mut self, zoom_factor: Vec2, center: PlotPoint) -> &mut Self { self.bounds_modifications .push(BoundsModification::Zoom(zoom_factor, center)); + + self } /// Scale the plot bounds around the hovered position, if any. @@ -141,9 +149,11 @@ impl<'a> PlotUi<'a> { /// The plot bounds are divided by `zoom_factor`, therefore: /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. - pub fn zoom_bounds_around_hovered(&mut self, zoom_factor: Vec2) { + pub fn zoom_bounds_around_hovered(&mut self, zoom_factor: Vec2) -> &mut Self { if let Some(hover_pos) = self.pointer_coordinate() { - self.zoom_bounds(zoom_factor, hover_pos); + self.zoom_bounds(zoom_factor, hover_pos) + } else { + self } } @@ -178,9 +188,9 @@ impl<'a> PlotUi<'a> { } /// Add a data line. - pub fn line(&mut self, mut line: Line) { + pub fn line(&mut self, mut line: Line) -> &mut Self { if line.series.is_empty() { - return; + return self; }; // Give the stroke an automatic color if no color has been assigned. @@ -188,12 +198,13 @@ impl<'a> PlotUi<'a> { line.stroke.color = self.auto_color(); } self.items.push(Box::new(line)); + self } /// Add a polygon. The polygon has to be convex. - pub fn polygon(&mut self, mut polygon: Polygon) { + pub fn polygon(&mut self, mut polygon: Polygon) -> &mut Self { if polygon.series.is_empty() { - return; + return self; }; // Give the stroke an automatic color if no color has been assigned. @@ -201,34 +212,48 @@ impl<'a> PlotUi<'a> { polygon.stroke.color = self.auto_color(); } self.items.push(Box::new(polygon)); + self } /// Add a text. - pub fn text(&mut self, text: Text) { + pub fn text(&mut self, text: Text) -> &mut Self { if text.text.is_empty() { - return; + return self; }; self.items.push(Box::new(text)); + self } /// Add data points. - pub fn points(&mut self, mut points: Points) { - if points.series.is_empty() { - return; - }; + pub fn owned_points(&mut self, mut points: Points) -> &mut Self { + // Give the points an automatic color if no color has been assigned. + if points.color == Color32::TRANSPARENT { + points.color = self.auto_color(); + } + self.items.push(Box::new(points)); + self + } + + /// Add data points. + pub fn borrowed_points<'b, T: 'a>(&mut self, mut points: Points) -> &mut Self + where + T: GenericPlotPoints, + { // Give the points an automatic color if no color has been assigned. if points.color == Color32::TRANSPARENT { points.color = self.auto_color(); } self.items.push(Box::new(points)); + + self } /// Add arrows. - pub fn arrows(&mut self, mut arrows: Arrows) { + pub fn arrows(&mut self, mut arrows: Arrows) -> &mut Self { if arrows.origins.is_empty() || arrows.tips.is_empty() { - return; + return self; }; // Give the arrows an automatic color if no color has been assigned. @@ -236,37 +261,45 @@ impl<'a> PlotUi<'a> { arrows.color = self.auto_color(); } self.items.push(Box::new(arrows)); + + self } /// Add an image. - pub fn image(&mut self, image: PlotImage) { + pub fn image(&mut self, image: PlotImage) -> &mut Self { self.items.push(Box::new(image)); + + self } /// Add a horizontal line. /// Can be useful e.g. to show min/max bounds or similar. /// Always fills the full width of the plot. - pub fn hline(&mut self, mut hline: HLine) { + pub fn hline(&mut self, mut hline: HLine) -> &mut Self { if hline.stroke.color == Color32::TRANSPARENT { hline.stroke.color = self.auto_color(); } self.items.push(Box::new(hline)); + + self } /// Add a vertical line. /// Can be useful e.g. to show min/max bounds or similar. /// Always fills the full height of the plot. - pub fn vline(&mut self, mut vline: VLine) { + pub fn vline(&mut self, mut vline: VLine) -> &mut Self { if vline.stroke.color == Color32::TRANSPARENT { vline.stroke.color = self.auto_color(); } self.items.push(Box::new(vline)); + + self } /// Add a box plot diagram. - pub fn box_plot(&mut self, mut box_plot: BoxPlot) { + pub fn box_plot(&mut self, mut box_plot: BoxPlot) -> &mut Self { if box_plot.boxes.is_empty() { - return; + return self; } // Give the elements an automatic color if no color has been assigned. @@ -274,12 +307,14 @@ impl<'a> PlotUi<'a> { box_plot = box_plot.color(self.auto_color()); } self.items.push(Box::new(box_plot)); + + self } /// Add a bar chart. - pub fn bar_chart(&mut self, mut chart: BarChart) { + pub fn bar_chart(&mut self, mut chart: BarChart) -> &mut Self { if chart.bars.is_empty() { - return; + return self; } // Give the elements an automatic color if no color has been assigned. @@ -287,5 +322,7 @@ impl<'a> PlotUi<'a> { chart = chart.color(self.auto_color()); } self.items.push(Box::new(chart)); + + self } } From 9fbdcb2d42d960ad75990dcac790a4bb0638f6a6 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:22:16 +0100 Subject: [PATCH 18/27] Make PlotUi and PlotUiBuilder public --- crates/egui_plot/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 56913cc063d..6c9036872fb 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -20,7 +20,7 @@ pub use items::{ Orientation, PlotImage, PlotPoint, PlotPoints, Points, Polygon, Text, VLine, }; pub use legend::{Corner, Legend}; -use plot_ui::{PlotUi, PlotUiBuilder}; +pub use plot_ui::{PlotUi, PlotUiBuilder}; pub use transform::{PlotBounds, PlotTransform}; use items::{horizontal_line, rulers_color, vertical_line}; From 655ab7cca119dd38f63428231aacb96f61fe1f1c Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:23:03 +0100 Subject: [PATCH 19/27] Fix demo and examples --- crates/egui_demo_lib/src/demo/context_menu.rs | 6 +- crates/egui_demo_lib/src/demo/plot_demo.rs | 134 +++++++++++------- .../egui_demo_lib/src/demo/widget_gallery.rs | 6 +- examples/custom_plot_manipulation/src/main.rs | 6 +- examples/save_plot/src/main.rs | 6 +- 5 files changed, 104 insertions(+), 54 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index ffcc73379d8..631285e6e36 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -149,7 +149,11 @@ impl ContextMenus { .width(self.width) .height(self.height) .data_aspect(1.0) - .show(ui, |plot_ui| plot_ui.line(line)) + .show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + plot_ui.line(line); + ((), plot_ui) + }) .response } diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 27f48f7ec4c..3d6e3d2c497 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -4,9 +4,9 @@ use std::ops::RangeInclusive; use egui::*; use egui_plot::{ - Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, - GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint, - PlotPoints, PlotResponse, Points, Polygon, Text, VLine, + Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, PlotPoint, + CoordinatesFormatter, Corner, GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, + Plot, PlotImage, PlotPoints, PlotResponse, PlotUi, Points, Polygon, Text, VLine, }; // ---------------------------------------------------------------------------- @@ -289,10 +289,13 @@ impl LineDemo { if self.coordinates { plot = plot.coordinates_formatter(Corner::LeftBottom, CoordinatesFormatter::default()); } - plot.show(ui, |plot_ui| { - plot_ui.line(self.circle()); - plot_ui.line(self.sin()); - plot_ui.line(self.thingy()); + plot.show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + plot_ui + .line(self.circle()) + .line(self.sin()) + .line(self.thingy()); + ((), plot_ui) }) .response } @@ -320,7 +323,7 @@ impl Default for MarkerDemo { } impl MarkerDemo { - fn markers(&self) -> Vec { + fn markers(&self) -> Vec> { MarkerShape::all() .enumerate() .map(|(i, marker)| { @@ -366,10 +369,12 @@ impl MarkerDemo { .data_aspect(1.0) .legend(Legend::default()); markers_plot - .show(ui, |plot_ui| { + .show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); for marker in self.markers() { - plot_ui.points(marker); + plot_ui.owned_points(marker); } + ((), plot_ui) }) .response } @@ -441,12 +446,17 @@ impl LegendDemo { .legend(config.clone()) .data_aspect(1.0); legend_plot - .show(ui, |plot_ui| { - plot_ui.line(Self::line_with_slope(0.5).name("lines")); - plot_ui.line(Self::line_with_slope(1.0).name("lines")); - plot_ui.line(Self::line_with_slope(2.0).name("lines")); - plot_ui.line(Self::sin().name("sin(x)")); - plot_ui.line(Self::cos().name("cos(x)")); + .show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + + plot_ui + .line(Self::line_with_slope(0.5).name("lines")) + .line(Self::line_with_slope(1.0).name("lines")) + .line(Self::line_with_slope(2.0).name("lines")) + .line(Self::sin().name("sin(x)")) + .line(Self::cos().name("cos(x)")); + + ((), plot_ui) }) .response } @@ -583,8 +593,10 @@ impl CustomAxesDemo { .custom_y_axes(y_axes) .x_grid_spacer(Self::x_grid) .label_formatter(label_fmt) - .show(ui, |plot_ui| { + .show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); plot_ui.line(Self::logistic_fn()); + ((), plot_ui) }) .response } @@ -636,12 +648,15 @@ impl LinkedAxesDemo { )) } - fn configure_plot(plot_ui: &mut egui_plot::PlotUi) { - plot_ui.line(Self::line_with_slope(0.5)); - plot_ui.line(Self::line_with_slope(1.0)); - plot_ui.line(Self::line_with_slope(2.0)); - plot_ui.line(Self::sin()); - plot_ui.line(Self::cos()); + fn configure_plot<'a>(plot_ui_builder: egui_plot::PlotUiBuilder) -> ((), PlotUi<'a>) { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + plot_ui + .line(Self::line_with_slope(0.5)) + .line(Self::line_with_slope(1.0)) + .line(Self::line_with_slope(2.0)) + .line(Self::sin()) + .line(Self::cos()); + ((), plot_ui) } fn ui(&mut self, ui: &mut Ui) -> Response { @@ -742,20 +757,25 @@ impl ItemsDemo { .show_x(false) .show_y(false) .data_aspect(1.0); - plot.show(ui, |plot_ui| { - plot_ui.hline(HLine::new(9.0).name("Lines horizontal")); - plot_ui.hline(HLine::new(-9.0).name("Lines horizontal")); - plot_ui.vline(VLine::new(9.0).name("Lines vertical")); - plot_ui.vline(VLine::new(-9.0).name("Lines vertical")); - plot_ui.line(line.name("Line with fill")); - plot_ui.polygon(polygon.name("Convex polygon")); - plot_ui.points(points.name("Points with stems")); - plot_ui.text(Text::new(PlotPoint::new(-3.0, -3.0), "wow").name("Text")); - plot_ui.text(Text::new(PlotPoint::new(-2.0, 2.5), "so graph").name("Text")); - plot_ui.text(Text::new(PlotPoint::new(3.0, 3.0), "much color").name("Text")); - plot_ui.text(Text::new(PlotPoint::new(2.5, -2.0), "such plot").name("Text")); - plot_ui.image(image.name("Image")); - plot_ui.arrows(arrows.name("Arrows")); + plot.show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + + plot_ui + .hline(HLine::new(9.0).name("Lines horizontal")) + .hline(HLine::new(-9.0).name("Lines horizontal")) + .vline(VLine::new(9.0).name("Lines vertical")) + .vline(VLine::new(-9.0).name("Lines vertical")) + .line(line.name("Line with fill")) + .polygon(polygon.name("Convex polygon")) + .owned_points(points.name("Points with stems")) + .text(Text::new(PlotPoint::new(-3.0, -3.0), "wow").name("Text")) + .text(Text::new(PlotPoint::new(-2.0, 2.5), "so graph").name("Text")) + .text(Text::new(PlotPoint::new(3.0, 3.0), "much color").name("Text")) + .text(Text::new(PlotPoint::new(2.5, -2.0), "such plot").name("Text")) + .image(image.name("Image")) + .arrows(arrows.name("Arrows")); + + ((), plot_ui) }) .response } @@ -775,14 +795,18 @@ impl InteractionDemo { response, inner: (screen_pos, pointer_coordinate, pointer_coordinate_drag_delta, bounds, hovered), .. - } = plot.show(ui, |plot_ui| { - ( + } = plot.show(ui, |plot_ui_builder| { + let plot_ui = plot_ui_builder.into_plot_ui(); + + let return_value = ( plot_ui.screen_from_plot(PlotPoint::new(0.0, 0.0)), plot_ui.pointer_coordinate(), plot_ui.pointer_coordinate_drag_delta(), plot_ui.plot_bounds(), plot_ui.response().hovered(), - ) + ); + + (return_value, plot_ui) }); ui.label(format!( @@ -912,7 +936,11 @@ impl ChartsDemo { .y_axis_width(3) .allow_zoom(self.allow_zoom) .allow_drag(self.allow_drag) - .show(ui, |plot_ui| plot_ui.bar_chart(chart)) + .show(ui, |plot_ui| { + let mut plot_ui = plot_ui.into_plot_ui(); + plot_ui.bar_chart(chart); + ((), plot_ui) + }) .response } @@ -971,11 +999,16 @@ impl ChartsDemo { .legend(Legend::default()) .data_aspect(1.0) .allow_drag(self.allow_drag) - .show(ui, |plot_ui| { - plot_ui.bar_chart(chart1); - plot_ui.bar_chart(chart2); - plot_ui.bar_chart(chart3); - plot_ui.bar_chart(chart4); + .show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + + plot_ui + .bar_chart(chart1) + .bar_chart(chart2) + .bar_chart(chart3) + .bar_chart(chart4); + + ((), plot_ui) }) .response } @@ -1016,10 +1049,11 @@ impl ChartsDemo { .legend(Legend::default()) .allow_zoom(self.allow_zoom) .allow_drag(self.allow_drag) - .show(ui, |plot_ui| { - plot_ui.box_plot(box1); - plot_ui.box_plot(box2); - plot_ui.box_plot(box3); + .show(ui, |plot_uit_builder| { + let mut plot_ui = plot_uit_builder.into_plot_ui(); + plot_ui.box_plot(box1).box_plot(box2).box_plot(box3); + + ((), plot_ui) }) .response } diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 4189e61c9ff..b9845ae61c8 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -269,7 +269,11 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response { .height(32.0) .show_axes(false) .data_aspect(1.0) - .show(ui, |plot_ui| plot_ui.line(line)) + .show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + plot_ui.line(line); + ((), plot_ui) + }) .response } diff --git a/examples/custom_plot_manipulation/src/main.rs b/examples/custom_plot_manipulation/src/main.rs index 4dffaf18ed1..2667fc49333 100644 --- a/examples/custom_plot_manipulation/src/main.rs +++ b/examples/custom_plot_manipulation/src/main.rs @@ -80,7 +80,9 @@ impl eframe::App for PlotExample { .allow_drag(false) .allow_scroll(false) .legend(Legend::default()) - .show(ui, |plot_ui| { + .show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + if let Some(mut scroll) = scroll { if modifiers.ctrl == self.ctrl_to_zoom { scroll = Vec2::splat(scroll.x + scroll.y); @@ -122,6 +124,8 @@ impl eframe::App for PlotExample { let sine_points = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000); plot_ui.line(Line::new(sine_points).name("Sine")); + + ((), plot_ui) }); }); } diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index 3f13d3f28b6..4a58f0ba098 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -32,8 +32,12 @@ impl eframe::App for MyApp { // let's create a dummy line in the plot let graph: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]]; - let inner = my_plot.show(ui, |plot_ui| { + let inner = my_plot.show(ui, |plot_ui_builder| { + let mut plot_ui = plot_ui_builder.into_plot_ui(); + plot_ui.line(Line::new(PlotPoints::from(graph)).name("curve")); + + ((), plot_ui) }); // Remember the position of the plot plot_rect = Some(inner.response.rect); From 525b4f1922be2df640d4b4ca6439565d80dd6d87 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:26:06 +0100 Subject: [PATCH 20/27] Cargo fmt --- crates/egui_demo_lib/src/demo/plot_demo.rs | 6 +++--- crates/egui_plot/src/items/mod.rs | 1 - crates/egui_plot/src/plot_ui.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 3d6e3d2c497..7ff8283e56c 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -4,9 +4,9 @@ use std::ops::RangeInclusive; use egui::*; use egui_plot::{ - Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, PlotPoint, - CoordinatesFormatter, Corner, GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, - Plot, PlotImage, PlotPoints, PlotResponse, PlotUi, Points, Polygon, Text, VLine, + Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, + GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint, + PlotPoints, PlotResponse, PlotUi, Points, Polygon, Text, VLine, }; // ---------------------------------------------------------------------------- diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 1fb984e3cf1..f136091e244 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -856,7 +856,6 @@ where } } - impl Points { pub fn new(series: impl Into) -> Self { Self { diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs index 9d1994b61b6..bec508f65d0 100644 --- a/crates/egui_plot/src/plot_ui.rs +++ b/crates/egui_plot/src/plot_ui.rs @@ -2,7 +2,7 @@ use egui::{epaint::Hsva, Color32, Context, Pos2, Response, Vec2, Vec2b}; use crate::{ items::{GenericPlotPoints, PlotItem}, - Arrows, BarChart, BoundsModification, BoxPlot, PlotPoint, HLine, Line, PlotBounds, PlotImage, + Arrows, BarChart, BoundsModification, BoxPlot, HLine, Line, PlotBounds, PlotImage, PlotPoint, PlotPoints, PlotTransform, Points, Polygon, Text, VLine, }; From 48cf8153a1e64b53bfa0237845e2e12373f35357 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:25:37 +0100 Subject: [PATCH 21/27] Add lifetime via PhantomData to Plot struct --- crates/egui_plot/src/lib.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 7d018f09d7a..2c0f6e86709 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -12,7 +12,7 @@ mod legend; mod memory; mod transform; -use std::{ops::RangeInclusive, sync::Arc}; +use std::{marker::PhantomData, ops::RangeInclusive, sync::Arc}; use egui::ahash::HashMap; use epaint::util::FloatOrd; @@ -152,7 +152,7 @@ pub struct PlotResponse { /// }); /// # }); /// ``` -pub struct Plot { +pub struct Plot<'a> { id_source: Id, id: Option, @@ -190,9 +190,11 @@ pub struct Plot { grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, + + phantom_plot_ui: std::marker::PhantomData>, } -impl Plot { +impl<'a> Plot<'a> { /// Give a unique id for each plot within the same [`Ui`]. pub fn new(id_source: impl std::hash::Hash) -> Self { Self { @@ -233,6 +235,8 @@ impl Plot { grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)], sharp_grid_lines: true, clamp_grid: false, + + phantom_plot_ui: PhantomData, } } @@ -697,9 +701,9 @@ impl Plot { /// Interact with and add items to the plot and finally draw it. - pub fn show<'a, F, R>(self, ui: &mut Ui, build_fn: F) -> PlotResponse + pub fn show(self, ui: &mut Ui, build_fn: F) -> PlotResponse where - F: FnOnce(PlotUiBuilder) -> (R, PlotUi<'a>), + F: FnOnce(&mut PlotUi<'a>) -> R, { let Self { id_source, @@ -736,6 +740,8 @@ impl Plot { clamp_grid, grid_spacers, sharp_grid_lines, + + phantom_plot_ui: _, } = self; // Determine position of widget. @@ -868,7 +874,8 @@ impl Plot { } = memory; // Call the plot build function. - let builder = PlotUiBuilder { + let mut plot_ui = PlotUi { + items: Vec::new(), next_auto_color_idx: 0, last_plot_transform, last_auto_bounds: auto_bounds, @@ -877,7 +884,7 @@ impl Plot { ctx: ui.ctx().clone(), }; - let (inner, plot_ui) = build_fn(builder); + let inner = build_fn(&mut plot_ui); let PlotUi { mut items, From 1ab8ff686691b174c9c3b60798304ca44d2597d8 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:26:24 +0100 Subject: [PATCH 22/27] Fix duplicate mod decls --- crates/egui_plot/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 2c0f6e86709..cca049861d4 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -36,11 +36,7 @@ use items::{horizontal_line, rulers_color, vertical_line}; pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}; pub use memory::PlotMemory; -mod axis; -mod items; -mod legend; mod plot_ui; -mod transform; type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String; type LabelFormatter = Option>; From 47583452a0958ceab844bc8e08bd1509474d8adc Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:27:33 +0100 Subject: [PATCH 23/27] Fix examples and demo --- crates/egui_demo_lib/src/demo/context_menu.rs | 4 +- crates/egui_demo_lib/src/demo/plot_demo.rs | 49 +++++-------------- .../egui_demo_lib/src/demo/widget_gallery.rs | 4 +- examples/custom_plot_manipulation/src/main.rs | 6 +-- examples/save_plot/src/main.rs | 6 +-- 5 files changed, 15 insertions(+), 54 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 631285e6e36..34b4b030f50 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -149,10 +149,8 @@ impl ContextMenus { .width(self.width) .height(self.height) .data_aspect(1.0) - .show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); + .show(ui, |plot_ui| { plot_ui.line(line); - ((), plot_ui) }) .response } diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index edabe436621..ae5b1832489 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -6,7 +6,7 @@ use egui::*; use egui_plot::{ Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint, - PlotPoints, PlotResponse, PlotUi, Points, Polygon, Text, VLine, + PlotPoints, PlotResponse, Points, Polygon, Text, VLine, }; // ---------------------------------------------------------------------------- @@ -289,13 +289,11 @@ impl LineDemo { if self.coordinates { plot = plot.coordinates_formatter(Corner::LeftBottom, CoordinatesFormatter::default()); } - plot.show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); + plot.show(ui, |plot_ui| { plot_ui .line(self.circle()) .line(self.sin()) .line(self.thingy()); - ((), plot_ui) }) .response } @@ -369,12 +367,10 @@ impl MarkerDemo { .data_aspect(1.0) .legend(Legend::default()); markers_plot - .show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); + .show(ui, |plot_ui| { for marker in self.markers() { plot_ui.owned_points(marker); } - ((), plot_ui) }) .response } @@ -446,17 +442,13 @@ impl LegendDemo { .legend(config.clone()) .data_aspect(1.0); legend_plot - .show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); - + .show(ui, |plot_ui| { plot_ui .line(Self::line_with_slope(0.5).name("lines")) .line(Self::line_with_slope(1.0).name("lines")) .line(Self::line_with_slope(2.0).name("lines")) .line(Self::sin().name("sin(x)")) .line(Self::cos().name("cos(x)")); - - ((), plot_ui) }) .response } @@ -593,10 +585,8 @@ impl CustomAxesDemo { .custom_y_axes(y_axes) .x_grid_spacer(Self::x_grid) .label_formatter(label_fmt) - .show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); + .show(ui, |plot_ui| { plot_ui.line(Self::logistic_fn()); - ((), plot_ui) }) .response } @@ -648,15 +638,13 @@ impl LinkedAxesDemo { )) } - fn configure_plot<'a>(plot_ui_builder: egui_plot::PlotUiBuilder) -> ((), PlotUi<'a>) { - let mut plot_ui = plot_ui_builder.into_plot_ui(); + fn configure_plot<'a>(plot_ui: &mut egui_plot::PlotUi<'a>) { plot_ui .line(Self::line_with_slope(0.5)) .line(Self::line_with_slope(1.0)) .line(Self::line_with_slope(2.0)) .line(Self::sin()) .line(Self::cos()); - ((), plot_ui) } fn ui(&mut self, ui: &mut Ui) -> Response { @@ -757,9 +745,7 @@ impl ItemsDemo { .show_x(false) .show_y(false) .data_aspect(1.0); - plot.show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); - + plot.show(ui, |plot_ui| { plot_ui .hline(HLine::new(9.0).name("Lines horizontal")) .hline(HLine::new(-9.0).name("Lines horizontal")) @@ -774,8 +760,6 @@ impl ItemsDemo { .text(Text::new(PlotPoint::new(2.5, -2.0), "such plot").name("Text")) .image(image.name("Image")) .arrows(arrows.name("Arrows")); - - ((), plot_ui) }) .response } @@ -808,9 +792,7 @@ impl InteractionDemo { response, inner: (screen_pos, pointer_coordinate, pointer_coordinate_drag_delta, bounds, hovered), .. - } = plot.show(ui, |plot_ui_builder| { - let plot_ui = plot_ui_builder.into_plot_ui(); - + } = plot.show(ui, |plot_ui| { let return_value = ( plot_ui.screen_from_plot(PlotPoint::new(0.0, 0.0)), plot_ui.pointer_coordinate(), @@ -819,7 +801,7 @@ impl InteractionDemo { plot_ui.response().hovered(), ); - (return_value, plot_ui) + return_value }); ui.label(format!( @@ -950,9 +932,7 @@ impl ChartsDemo { .allow_zoom(self.allow_zoom) .allow_drag(self.allow_drag) .show(ui, |plot_ui| { - let mut plot_ui = plot_ui.into_plot_ui(); plot_ui.bar_chart(chart); - ((), plot_ui) }) .response } @@ -1012,16 +992,12 @@ impl ChartsDemo { .legend(Legend::default()) .data_aspect(1.0) .allow_drag(self.allow_drag) - .show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); - + .show(ui, |plot_ui| { plot_ui .bar_chart(chart1) .bar_chart(chart2) .bar_chart(chart3) .bar_chart(chart4); - - ((), plot_ui) }) .response } @@ -1062,11 +1038,8 @@ impl ChartsDemo { .legend(Legend::default()) .allow_zoom(self.allow_zoom) .allow_drag(self.allow_drag) - .show(ui, |plot_uit_builder| { - let mut plot_ui = plot_uit_builder.into_plot_ui(); + .show(ui, |plot_ui| { plot_ui.box_plot(box1).box_plot(box2).box_plot(box3); - - ((), plot_ui) }) .response } diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index b9845ae61c8..b3c837fe922 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -269,10 +269,8 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response { .height(32.0) .show_axes(false) .data_aspect(1.0) - .show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); + .show(ui, |plot_ui| { plot_ui.line(line); - ((), plot_ui) }) .response } diff --git a/examples/custom_plot_manipulation/src/main.rs b/examples/custom_plot_manipulation/src/main.rs index 2667fc49333..4dffaf18ed1 100644 --- a/examples/custom_plot_manipulation/src/main.rs +++ b/examples/custom_plot_manipulation/src/main.rs @@ -80,9 +80,7 @@ impl eframe::App for PlotExample { .allow_drag(false) .allow_scroll(false) .legend(Legend::default()) - .show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); - + .show(ui, |plot_ui| { if let Some(mut scroll) = scroll { if modifiers.ctrl == self.ctrl_to_zoom { scroll = Vec2::splat(scroll.x + scroll.y); @@ -124,8 +122,6 @@ impl eframe::App for PlotExample { let sine_points = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000); plot_ui.line(Line::new(sine_points).name("Sine")); - - ((), plot_ui) }); }); } diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index 4a58f0ba098..3f13d3f28b6 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -32,12 +32,8 @@ impl eframe::App for MyApp { // let's create a dummy line in the plot let graph: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]]; - let inner = my_plot.show(ui, |plot_ui_builder| { - let mut plot_ui = plot_ui_builder.into_plot_ui(); - + let inner = my_plot.show(ui, |plot_ui| { plot_ui.line(Line::new(PlotPoints::from(graph)).name("curve")); - - ((), plot_ui) }); // Remember the position of the plot plot_rect = Some(inner.response.rect); From ff245bb48e389965c380b575d1d87040bc4b85ff Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:38:28 +0100 Subject: [PATCH 24/27] Fix doc tests --- crates/egui_plot/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index cca049861d4..766459ac273 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -140,11 +140,8 @@ pub struct PlotResponse { /// [x, x.sin()] /// }).collect(); /// let line = Line::new(sin); -/// Plot::new("my_plot").view_aspect(2.0).show(ui, |builder| { -/// let mut plot_ui = builder.into_plot_ui(); +/// Plot::new("my_plot").view_aspect(2.0).show(ui, |plot_ui| { /// plot_ui.line(line); -/// -/// ((), plot_ui) /// }); /// # }); /// ``` @@ -398,7 +395,9 @@ impl<'a> Plot<'a> { /// "".to_owned() /// } /// }) - /// .show(ui, |plot_ui| plot_ui.line(line)); + /// .show(ui, |plot_ui| { + /// plot_ui.line(line); + /// }); /// # }); /// ``` pub fn label_formatter( From 5206472bfca9efdf4437fa333feff58e3eefd9af Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Thu, 25 Jan 2024 14:27:13 +0100 Subject: [PATCH 25/27] Added scatter plot demo iter example --- crates/egui_demo_lib/src/demo/plot_demo.rs | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index ae5b1832489..9b5c69905ee 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -21,6 +21,7 @@ enum Panel { Interaction, CustomAxes, LinkedAxes, + ScatterPlot, } impl Default for Panel { @@ -41,6 +42,7 @@ pub struct PlotDemo { interaction_demo: InteractionDemo, custom_axes_demo: CustomAxesDemo, linked_axes_demo: LinkedAxesDemo, + scatter_plot: ScatterPlot, open_panel: Panel, } @@ -87,6 +89,7 @@ impl super::View for PlotDemo { ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction"); ui.selectable_value(&mut self.open_panel, Panel::CustomAxes, "Custom Axes"); ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes"); + ui.selectable_value(&mut self.open_panel, Panel::ScatterPlot, "Scatter Plot"); }); ui.separator(); @@ -115,6 +118,9 @@ impl super::View for PlotDemo { Panel::LinkedAxes => { self.linked_axes_demo.ui(ui); } + Panel::ScatterPlot => { + self.scatter_plot.ui(ui); + } } } } @@ -693,6 +699,97 @@ impl LinkedAxesDemo { // ---------------------------------------------------------------------------- +struct ScatterPlot { + points: Vec, + step: usize, + point_radius: f32, + fill_points: bool, +} + +impl PartialEq for ScatterPlot { + fn eq(&self, other: &Self) -> bool { + self.point_radius == other.point_radius && self.fill_points == other.fill_points + } +} + +impl Default for ScatterPlot { + fn default() -> Self { + Self::new() + } +} + +impl ScatterPlot { + fn new() -> Self { + Self { + points: Self::calculate_points(), + step: 1, + point_radius: 1.5, + fill_points: true, + } + } + + fn calculate_points() -> Vec { + (0..50_000) + .zip((-10..10).cycle()) + .filter_map(|(x, offset)| { + if offset != 0 { + let x = x as f64 / 1000.0; + let y = x * 1.5 + offset as f64; + Some(PlotPoint::new(x, y)) + } else { + None + } + }) + .collect() + } + + fn ui(&mut self, ui: &mut Ui) -> Response { + ui.label("Plot iterators!"); + ui.add( + egui::DragValue::new(&mut self.step) + .speed(1) + .clamp_range(1..=100) + .prefix("Filter point step: "), + ); + ui.horizontal(|ui| { + ui.checkbox(&mut self.fill_points, "Fill"); + ui.add( + egui::DragValue::new(&mut self.point_radius) + .speed(0.1) + .clamp_range(0.0..=f64::INFINITY) + .prefix("Radius: "), + ); + }); + + Plot::new("scatter_plot") + .legend(Legend::default().position(Corner::LeftTop)) + .y_axis_width(4) + .show_axes(true) + .show_grid(true) + // .view_aspect(1.0) + // .data_aspect(1.0) + .show(ui, |plot_ui| { + let points = Points::new_generic(self.points.iter().step_by(self.step)) + .name("Points") + .filled(self.fill_points) + .radius(self.point_radius); + + plot_ui.borrowed_points(points).line( + Line::new(PlotPoints::from_explicit_callback( + |x| x * 1.5, + 0.0..=50.0, + 100, + )) + .name("Line") + .width(self.point_radius * 1.5), + ); + }) + .response + } +} + +// ---------------------------------------------------------------------------- + #[derive(PartialEq, Default)] struct ItemsDemo { texture: Option, From 231e0b3454024492928dd5169e014dab750415f4 Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Thu, 25 Jan 2024 14:27:32 +0100 Subject: [PATCH 26/27] Removed unnecessary lifetime --- crates/egui_demo_lib/src/demo/plot_demo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 9b5c69905ee..0f69956c271 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -644,7 +644,7 @@ impl LinkedAxesDemo { )) } - fn configure_plot<'a>(plot_ui: &mut egui_plot::PlotUi<'a>) { + fn configure_plot(plot_ui: &mut egui_plot::PlotUi<'_>) { plot_ui .line(Self::line_with_slope(0.5)) .line(Self::line_with_slope(1.0)) From e4cfe4a2c4cecd8b37c30dc975815f07a82de46b Mon Sep 17 00:00:00 2001 From: Gunnar Noordbruis <13799935+GunnarMorrigan@users.noreply.github.com> Date: Thu, 25 Jan 2024 14:27:56 +0100 Subject: [PATCH 27/27] Fix doc Plot path --- crates/egui_plot/src/plot_ui.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs index bec508f65d0..64b9f1a58a9 100644 --- a/crates/egui_plot/src/plot_ui.rs +++ b/crates/egui_plot/src/plot_ui.rs @@ -62,7 +62,7 @@ impl From for PlotUi<'_> { } /// Provides methods to interact with a plot while building it. It is the single argument of the closure -/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. +/// provided to [`crate::Plot::show`]. See [`crate::Plot`] for an example of how to use it. pub struct PlotUi<'a> { pub(crate) items: Vec>, pub(crate) next_auto_color_idx: usize, @@ -110,7 +110,7 @@ impl<'a> PlotUi<'a> { } /// Whether the plot axes were in auto-bounds mode in the last frame. If called on the first - /// frame, this is the [`Plot`]'s default auto-bounds mode. + /// frame, this is the [`crate::Plot`]'s default auto-bounds mode. pub fn auto_bounds(&self) -> Vec2b { self.last_auto_bounds }