From 0bad1d0c99033b820669f280891524897f0518c1 Mon Sep 17 00:00:00 2001 From: Sven Niederberger Date: Sat, 13 Nov 2021 11:56:22 +0100 Subject: [PATCH] Plot interaction methods (#766) * move to a basic plot builder with callback * add some interaction methods * move interaction demo to its own panel --- CHANGELOG.md | 3 + egui/src/widgets/plot/items.rs | 10 + egui/src/widgets/plot/mod.rs | 295 ++++++++++-------- egui/src/widgets/plot/transform.rs | 16 +- egui_demo_lib/src/apps/demo/context_menu.rs | 6 +- egui_demo_lib/src/apps/demo/plot_demo.rs | 116 ++++--- egui_demo_lib/src/apps/demo/widget_gallery.rs | 7 +- 7 files changed, 277 insertions(+), 176 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1188123360a..ab36d46582c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * You can now read and write the cursor of a `TextEdit` ([#848](https://github.com/emilk/egui/pull/848)). * Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)). * When using a custom font you can now specify a font index ([#873](https://github.com/emilk/egui/pull/873)). +* You can now read the plot coordinates of the mouse when building a `Plot` ([#766](https://github.com/emilk/egui/pull/766)). ### Changed 🔧 * Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)). @@ -19,6 +20,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Replace `CtxRef::begin_frame` and `end_frame` with `CtxRef::run` ([#872](https://github.com/emilk/egui/pull/872)). * Replace `Ui::__test` with `egui::__run_test_ui` ([#872](https://github.com/emilk/egui/pull/872)). * Replace `scroll_delta` and `zoom_delta` in `RawInput` with `Event::Scroll` and `Event::Zoom`. +* Plots now provide a `show` method that has to be used to add items to and show the plot ([#766](https://github.com/emilk/egui/pull/766)). ### Fixed 🐛 * Fix `ComboBox` and other popups getting clipped to parent window ([#885](https://github.com/emilk/egui/pull/885)). @@ -33,6 +35,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * [sumibi-yakitori](https://github.com/sumibi-yakitori) ([#830](https://github.com/emilk/egui/pull/830)) * [5225225](https://github.com/5225225): ([#849](https://github.com/emilk/egui/pull/849)). * [t18b219k](https://github.com/t18b219k): ([#868](https://github.com/emilk/egui/pull/868)). +* [EmbersArc](https://github.com/EmbersArc): ([#766](https://github.com/emilk/egui/pull/766)). ## 0.15.0 - 2021-10-24 - Syntax highlighting and hscroll diff --git a/egui/src/widgets/plot/items.rs b/egui/src/widgets/plot/items.rs index 6608b12a025..fc691331c81 100644 --- a/egui/src/widgets/plot/items.rs +++ b/egui/src/widgets/plot/items.rs @@ -30,6 +30,16 @@ impl Value { y: y.into(), } } + + #[inline(always)] + pub fn to_pos2(self) -> Pos2 { + Pos2::new(self.x as f32, self.y as f32) + } + + #[inline(always)] + pub fn to_vec2(self) -> Vec2 { + Vec2::new(self.x as f32, self.y as f32) + } } // ---------------------------------------------------------------------------- diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index ae0e610c7aa..3085dc0127b 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -28,6 +28,7 @@ struct PlotMemory { hovered_entry: Option, hidden_items: AHashSet, min_auto_bounds: Bounds, + last_screen_transform: Option, } impl PlotMemory { @@ -54,16 +55,11 @@ impl PlotMemory { /// Value::new(x, x.sin()) /// }); /// let line = Line::new(Values::from_values_iter(sin)); -/// ui.add( -/// Plot::new("my_plot").line(line).view_aspect(2.0) -/// ); +/// Plot::new("my_plot").view_aspect(2.0).show(ui, |plot_ui| plot_ui.line(line)); /// # }); /// ``` pub struct Plot { id_source: Id, - next_auto_color_idx: usize, - - items: Vec>, center_x_axis: bool, center_y_axis: bool, @@ -90,9 +86,6 @@ impl Plot { pub fn new(id_source: impl std::hash::Hash) -> Self { Self { id_source: Id::new(id_source), - next_auto_color_idx: 0, - - items: Default::default(), center_x_axis: false, center_y_axis: false, @@ -115,108 +108,6 @@ impl Plot { } } - 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: OkLab or some other perspective color space - } - - /// Add a data lines. - pub fn line(mut self, mut line: Line) -> Self { - if line.series.is_empty() { - return self; - }; - - // 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)); - self - } - - /// Add a polygon. The polygon has to be convex. - pub fn polygon(mut self, mut polygon: Polygon) -> Self { - if polygon.series.is_empty() { - return self; - }; - - // 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)); - self - } - - /// Add a text. - pub fn text(mut self, text: Text) -> Self { - if text.text.is_empty() { - return self; - }; - - self.items.push(Box::new(text)); - self - } - - /// Add data points. - pub fn points(mut self, mut points: Points) -> Self { - if points.series.is_empty() { - return 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 arrows. - pub fn arrows(mut self, mut arrows: Arrows) -> Self { - if arrows.origins.is_empty() || arrows.tips.is_empty() { - return self; - }; - - // 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)); - self - } - - /// Add an image. - pub fn image(mut self, image: PlotImage) -> 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) -> 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) -> Self { - if vline.stroke.color == Color32::TRANSPARENT { - vline.stroke.color = self.auto_color(); - } - self.items.push(Box::new(vline)); - self - } - /// width / height ratio of the data. /// For instance, it can be useful to set this to `1.0` for when the two axes show the same /// unit. @@ -326,14 +217,11 @@ impl Plot { self.show_axes = show; self } -} -impl Widget for Plot { - fn ui(self, ui: &mut Ui) -> Response { + /// 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)) -> Response { let Self { id_source, - next_auto_color_idx: _, - mut items, center_x_axis, center_y_axis, allow_zoom, @@ -359,6 +247,7 @@ impl Widget for Plot { hovered_entry: None, hidden_items: Default::default(), min_auto_bounds, + last_screen_transform: None, }); // If the min bounds changed, recalculate everything. @@ -378,6 +267,7 @@ impl Widget for Plot { mut auto_bounds, mut hovered_entry, mut hidden_items, + last_screen_transform, .. } = memory; @@ -408,6 +298,20 @@ impl Widget for Plot { let (rect, response) = ui.allocate_exact_size(size, Sense::drag()); let plot_painter = ui.painter().sub_region(rect); + // Call the plot build function. + let mut plot_ui = PlotUi { + items: Vec::new(), + next_auto_color_idx: 0, + last_screen_transform, + response, + }; + build_fn(&mut plot_ui); + let PlotUi { + mut items, + response, + .. + } = plot_ui; + // Background if show_background { plot_painter.add(epaint::RectShape { @@ -421,7 +325,6 @@ impl Widget for Plot { // Legend let legend = legend_config .and_then(|config| LegendWidget::try_new(rect, config, &items, &hidden_items)); - // Don't show hover cursor when hovering over legend. if hovered_entry.is_some() { show_x = false; @@ -439,6 +342,7 @@ impl Widget for Plot { // Move highlighted items to front. items.sort_by_key(|item| item.highlighted()); + // Allow double clicking to reset to automatic bounds. auto_bounds |= response.double_clicked_by(PointerButton::Primary); // Set bounds automatically based on content. @@ -449,18 +353,6 @@ impl Widget for Plot { .for_each(|item| bounds.merge(&item.get_bounds())); bounds.add_relative_margin(margin_fraction); } - // Make sure they are not empty. - if !bounds.is_valid() { - bounds = Bounds::new_symmetrical(1.0); - } - - // Scale axes so that the origin is in the center. - if center_x_axis { - bounds.make_x_symmetrical(); - }; - if center_y_axis { - bounds.make_y_symmetrical(); - }; let mut transform = ScreenTransform::new(rect, bounds, center_x_axis, center_y_axis); @@ -503,12 +395,12 @@ impl Widget for Plot { let bounds = *transform.bounds(); - let prepared = Prepared { + let prepared = PreparedPlot { items, show_x, show_y, show_axes, - transform, + transform: transform.clone(), }; prepared.ui(ui, &response); @@ -524,6 +416,7 @@ impl Widget for Plot { hovered_entry, hidden_items, min_auto_bounds, + last_screen_transform: Some(transform), }; memory.store(ui.ctx(), plot_id); @@ -535,7 +428,143 @@ impl Widget for Plot { } } -struct Prepared { +/// 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_screen_transform: Option, + response: Response, +} + +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: OkLab or some other perspective color space + } + + /// The pointer position in plot coordinates, if the pointer is inside the plot area. + pub fn pointer_coordinate(&self) -> Option { + let last_screen_transform = self.last_screen_transform.as_ref()?; + // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: + let last_pos = self.response.hover_pos()? - self.response.drag_delta(); + let value = last_screen_transform.value_from_position(last_pos); + Some(value) + } + + /// The pointer drag delta in plot coordinates. + pub fn pointer_coordinate_drag_delta(&self) -> Vec2 { + self.last_screen_transform + .as_ref() + .map_or(Vec2::ZERO, |tf| { + let delta = self.response.drag_delta(); + let dp_dv = tf.dpos_dvalue(); + Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) + }) + } + + /// Transform the screen coordinates to plot coordinates. + pub fn plot_from_screen(&self, position: Pos2) -> Pos2 { + self.last_screen_transform + .as_ref() + .map_or(Pos2::ZERO, |tf| { + tf.position_from_value(&Value::new(position.x as f64, position.y as f64)) + }) + // We need to subtract the drag delta since the last frame. + - self.response.drag_delta() + } + + /// 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)); + } +} + +struct PreparedPlot { items: Vec>, show_x: bool, show_y: bool, @@ -543,7 +572,7 @@ struct Prepared { transform: ScreenTransform, } -impl Prepared { +impl PreparedPlot { fn ui(self, ui: &mut Ui, response: &Response) { let mut shapes = Vec::new(); diff --git a/egui/src/widgets/plot/transform.rs b/egui/src/widgets/plot/transform.rs index 9de9d32d735..058fd7ec6d8 100644 --- a/egui/src/widgets/plot/transform.rs +++ b/egui/src/widgets/plot/transform.rs @@ -118,6 +118,7 @@ impl Bounds { } /// Contains the screen rectangle and the plot bounds and provides methods to transform them. +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone)] pub(crate) struct ScreenTransform { /// The screen rectangle. @@ -131,7 +132,20 @@ pub(crate) struct ScreenTransform { } impl ScreenTransform { - pub fn new(frame: Rect, bounds: Bounds, x_centered: bool, y_centered: bool) -> Self { + pub fn new(frame: Rect, mut bounds: Bounds, x_centered: bool, y_centered: bool) -> Self { + // Make sure they are not empty. + if !bounds.is_valid() { + bounds = Bounds::new_symmetrical(1.0); + } + + // Scale axes so that the origin is in the center. + if x_centered { + bounds.make_x_symmetrical(); + }; + if y_centered { + bounds.make_y_symmetrical(); + }; + Self { frame, bounds, diff --git a/egui_demo_lib/src/apps/demo/context_menu.rs b/egui_demo_lib/src/apps/demo/context_menu.rs index 627ef4e867a..fb8fff7c9bd 100644 --- a/egui_demo_lib/src/apps/demo/context_menu.rs +++ b/egui_demo_lib/src/apps/demo/context_menu.rs @@ -69,7 +69,7 @@ impl super::View for ContextMenus { ui.label("Right-click plot to edit it!"); ui.horizontal(|ui| { - ui.add(self.example_plot()).context_menu(|ui| { + self.example_plot(ui).context_menu(|ui| { ui.menu_button("Plot", |ui| { if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked() || ui @@ -109,7 +109,7 @@ impl super::View for ContextMenus { } impl ContextMenus { - fn example_plot(&self) -> egui::plot::Plot { + fn example_plot(&self, ui: &mut egui::Ui) -> egui::Response { use egui::plot::{Line, Value, Values}; let n = 128; let line = Line::new(Values::from_values_iter((0..=n).map(|i| { @@ -127,10 +127,10 @@ impl ContextMenus { .allow_zoom(self.allow_zoom) .center_x_axis(self.center_x_axis) .center_x_axis(self.center_y_axis) - .line(line) .width(self.width) .height(self.height) .data_aspect(1.0) + .show(ui, |plot_ui| plot_ui.line(line)) } fn nested_menus(ui: &mut egui::Ui) { diff --git a/egui_demo_lib/src/apps/demo/plot_demo.rs b/egui_demo_lib/src/apps/demo/plot_demo.rs index 5042a2dd450..a85d305cf99 100644 --- a/egui_demo_lib/src/apps/demo/plot_demo.rs +++ b/egui_demo_lib/src/apps/demo/plot_demo.rs @@ -143,18 +143,18 @@ impl Widget for &mut LineDemo { ui.ctx().request_repaint(); self.time += ui.input().unstable_dt.at_most(1.0 / 30.0) as f64; }; - let mut plot = Plot::new("lines_demo") - .line(self.circle()) - .line(self.sin()) - .line(self.thingy()) - .legend(Legend::default()); + let mut plot = Plot::new("lines_demo").legend(Legend::default()); if self.square { plot = plot.view_aspect(1.0); } if self.proportional { plot = plot.data_aspect(1.0); } - ui.add(plot) + plot.show(ui, |plot_ui| { + plot_ui.line(self.circle()); + plot_ui.line(self.sin()); + plot_ui.line(self.thingy()); + }) } } @@ -222,13 +222,14 @@ impl Widget for &mut MarkerDemo { } }); - let mut markers_plot = Plot::new("markers_demo") + let markers_plot = Plot::new("markers_demo") .data_aspect(1.0) .legend(Legend::default()); - for marker in self.markers() { - markers_plot = markers_plot.points(marker); - } - ui.add(markers_plot) + markers_plot.show(ui, |plot_ui| { + for marker in self.markers() { + plot_ui.points(marker); + } + }) } } @@ -287,15 +288,14 @@ impl Widget for &mut LegendDemo { ui.end_row(); }); - let legend_plot = Plot::new("legend_demo") - .line(LegendDemo::line_with_slope(0.5).name("lines")) - .line(LegendDemo::line_with_slope(1.0).name("lines")) - .line(LegendDemo::line_with_slope(2.0).name("lines")) - .line(LegendDemo::sin().name("sin(x)")) - .line(LegendDemo::cos().name("cos(x)")) - .legend(*config) - .data_aspect(1.0); - ui.add(legend_plot) + let legend_plot = Plot::new("legend_demo").legend(*config).data_aspect(1.0); + legend_plot.show(ui, |plot_ui| { + plot_ui.line(LegendDemo::line_with_slope(0.5).name("lines")); + plot_ui.line(LegendDemo::line_with_slope(1.0).name("lines")); + plot_ui.line(LegendDemo::line_with_slope(2.0).name("lines")); + plot_ui.line(LegendDemo::sin().name("sin(x)")); + plot_ui.line(LegendDemo::cos().name("cos(x)")); + }) } } @@ -347,24 +347,64 @@ impl Widget for &mut ItemsDemo { ); let plot = Plot::new("items_demo") - .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")) - .points(points.name("Points with stems")) - .text(Text::new(Value::new(-3.0, -3.0), "wow").name("Text")) - .text(Text::new(Value::new(-2.0, 2.5), "so graph").name("Text")) - .text(Text::new(Value::new(3.0, 3.0), "much color").name("Text")) - .text(Text::new(Value::new(2.5, -2.0), "such plot").name("Text")) - .image(image.name("Image")) - .arrows(arrows.name("Arrows")) .legend(Legend::default().position(Corner::RightBottom)) .show_x(false) .show_y(false) .data_aspect(1.0); - ui.add(plot) + 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(Value::new(-3.0, -3.0), "wow").name("Text")); + plot_ui.text(Text::new(Value::new(-2.0, 2.5), "so graph").name("Text")); + plot_ui.text(Text::new(Value::new(3.0, 3.0), "much color").name("Text")); + plot_ui.text(Text::new(Value::new(2.5, -2.0), "such plot").name("Text")); + plot_ui.image(image.name("Image")); + plot_ui.arrows(arrows.name("Arrows")); + }) + } +} + +#[derive(PartialEq)] +struct InteractionDemo { + pointer_coordinate: Option, + pointer_coordinate_drag_delta: Vec2, +} + +impl Default for InteractionDemo { + fn default() -> Self { + Self { + pointer_coordinate: None, + pointer_coordinate_drag_delta: Vec2::ZERO, + } + } +} + +impl Widget for &mut InteractionDemo { + fn ui(self, ui: &mut Ui) -> Response { + let coordinate_text = if let Some(coordinate) = self.pointer_coordinate { + format!("x: {:.03}, y: {:.03}", coordinate.x, coordinate.y) + } else { + "None".to_string() + }; + ui.label(format!("pointer coordinate: {}", coordinate_text)); + let coordinate_text = format!( + "x: {:.03}, y: {:.03}", + self.pointer_coordinate_drag_delta.x, self.pointer_coordinate_drag_delta.y + ); + ui.label(format!( + "pointer coordinate drag delta: {}", + coordinate_text + )); + let plot = Plot::new("interaction_demo"); + plot.show(ui, |plot_ui| { + self.pointer_coordinate = plot_ui.pointer_coordinate(); + self.pointer_coordinate_drag_delta = plot_ui.pointer_coordinate_drag_delta(); + }) } } @@ -374,6 +414,7 @@ enum Panel { Markers, Legend, Items, + Interaction, } impl Default for Panel { @@ -388,6 +429,7 @@ pub struct PlotDemo { marker_demo: MarkerDemo, legend_demo: LegendDemo, items_demo: ItemsDemo, + interaction_demo: InteractionDemo, open_panel: Panel, } @@ -413,7 +455,7 @@ impl super::View for PlotDemo { ui.collapsing("Instructions", |ui| { ui.label("Pan by dragging, or scroll (+ shift = horizontal)."); if cfg!(target_arch = "wasm32") { - ui.label("Zoom with ctrl / ⌘ + mouse wheel, or with pinch gesture."); + ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture."); } else if cfg!(target_os = "macos") { ui.label("Zoom with ctrl / ⌘ + scroll."); } else { @@ -429,6 +471,7 @@ impl super::View for PlotDemo { ui.selectable_value(&mut self.open_panel, Panel::Markers, "Markers"); ui.selectable_value(&mut self.open_panel, Panel::Legend, "Legend"); ui.selectable_value(&mut self.open_panel, Panel::Items, "Items"); + ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction"); }); ui.separator(); @@ -445,6 +488,9 @@ impl super::View for PlotDemo { Panel::Items => { ui.add(&mut self.items_demo); } + Panel::Interaction => { + ui.add(&mut self.interaction_demo); + } } } } diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index c15da2dfdb4..8d2cf2760f9 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -211,8 +211,7 @@ impl WidgetGallery { ui.end_row(); ui.add(doc_link_label("Plot", "plot")); - ui.add(example_plot()); - + example_plot(ui); ui.end_row(); ui.hyperlink_to( @@ -227,7 +226,7 @@ impl WidgetGallery { } } -fn example_plot() -> egui::plot::Plot { +fn example_plot(ui: &mut egui::Ui) -> egui::Response { use egui::plot::{Line, Value, Values}; let n = 128; let line = Line::new(Values::from_values_iter((0..=n).map(|i| { @@ -236,9 +235,9 @@ fn example_plot() -> egui::plot::Plot { Value::new(x, x.sin()) }))); egui::plot::Plot::new("example_plot") - .line(line) .height(32.0) .data_aspect(1.0) + .show(ui, |plot_ui| plot_ui.line(line)) } fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a {