From 438f6eafc840a50236dd16ef298f69fba63e457a Mon Sep 17 00:00:00 2001 From: Jackson Kruger Date: Tue, 18 Apr 2023 10:58:19 -0700 Subject: [PATCH] Plot widget - allow disabling zoom and drag for x and y separately (#2901) * Set whether zooming allowed for x and y separately * Set whether dragging allowed for x and y separately * Add disclaimers about interaction with data_aspect * Show zoom & drag behavior in plot demo/charts instead of context menu demo * Code review suggestions - use AxisBools::any() Co-authored-by: Emil Ernerfeldt * Simplify allow_drag and allow_zoom APIs to take in Into * Remove unnecessary using... * Remove unrelated change --------- Co-authored-by: Emil Ernerfeldt --- CHANGELOG.md | 2 +- crates/egui/src/widgets/plot/mod.rs | 59 +++++++++++++++------- crates/egui_demo_lib/src/demo/plot_demo.rs | 49 ++++++++++++++---- 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b0bf9a..008bcda0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG ## Unreleased * Add `char_limit` to `TextEdit` singleline mode to limit the amount of characters * ⚠️ BREAKING: `Plot::link_axis` and `Plot::link_cursor` now take the name of the group ([#2410](https://github.com/emilk/egui/pull/2410)). - +* Update `Plot::allow_zoom` and `Plot::allow_drag` to allow setting those values for X and Y axes independently ([#2901](https://github.com/emilk/egui/pull/2901)). ## 0.21.0 - 2023-02-08 - Deadlock fix and style customizability * ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)): diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index 8797d61d..280baf22 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -72,15 +72,19 @@ impl Default for CoordinatesFormatter { const MIN_LINE_SPACING_IN_POINTS: f64 = 6.0; // TODO(emilk): large enough for a wide label #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Copy, Clone)] -struct AxisBools { - x: bool, - y: bool, +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct AxisBools { + pub x: bool, + pub y: bool, } impl AxisBools { + pub fn new(x: bool, y: bool) -> Self { + Self { x, y } + } + #[inline] - fn any(&self) -> bool { + pub fn any(&self) -> bool { self.x || self.y } } @@ -165,8 +169,8 @@ pub struct Plot { center_x_axis: bool, center_y_axis: bool, - allow_zoom: bool, - allow_drag: bool, + allow_zoom: AxisBools, + allow_drag: AxisBools, allow_scroll: bool, allow_double_click_reset: bool, allow_boxed_zoom: bool, @@ -207,8 +211,8 @@ impl Plot { center_x_axis: false, center_y_axis: false, - allow_zoom: true, - allow_drag: true, + allow_zoom: true.into(), + allow_drag: true.into(), allow_scroll: true, allow_double_click_reset: true, allow_boxed_zoom: true, @@ -305,8 +309,13 @@ impl Plot { } /// Whether to allow zooming in the plot. Default: `true`. - pub fn allow_zoom(mut self, on: bool) -> Self { - self.allow_zoom = on; + /// + /// Note: Allowing zoom in one axis but not the other may lead to unexpected results if used in combination with `data_aspect`. + pub fn allow_zoom(mut self, on: T) -> Self + where + T: Into, + { + self.allow_zoom = on.into(); self } @@ -346,8 +355,11 @@ impl Plot { } /// Whether to allow dragging in the plot to move the bounds. Default: `true`. - pub fn allow_drag(mut self, on: bool) -> Self { - self.allow_drag = on; + pub fn allow_drag(mut self, on: T) -> Self + where + T: Into, + { + self.allow_drag = on.into(); self } @@ -829,9 +841,16 @@ impl Plot { } // Dragging - if allow_drag && response.dragged_by(PointerButton::Primary) { + if allow_drag.any() && response.dragged_by(PointerButton::Primary) { response = response.on_hover_cursor(CursorIcon::Grabbing); - transform.translate_bounds(-response.drag_delta()); + let mut delta = -response.drag_delta(); + if !allow_drag.x { + delta.x = 0.0; + } + if !allow_drag.y { + delta.y = 0.0; + } + transform.translate_bounds(delta); bounds_modified = true.into(); } @@ -889,12 +908,18 @@ impl Plot { let hover_pos = response.hover_pos(); if let Some(hover_pos) = hover_pos { - if allow_zoom { - let zoom_factor = if data_aspect.is_some() { + if allow_zoom.any() { + let mut zoom_factor = if data_aspect.is_some() { Vec2::splat(ui.input(|i| i.zoom_delta())) } else { ui.input(|i| i.zoom_delta_2d()) }; + if !allow_zoom.x { + zoom_factor.x = 1.0; + } + if !allow_zoom.y { + zoom_factor.y = 1.0; + } if zoom_factor != Vec2::splat(1.0) { transform.zoom(zoom_factor, hover_pos); bounds_modified = true.into(); diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 0865bf7a..ef15a66b 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -1,7 +1,7 @@ use std::f64::consts::TAU; use std::ops::RangeInclusive; -use egui::plot::{GridInput, GridMark}; +use egui::plot::{AxisBools, GridInput, GridMark}; use egui::*; use plot::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine, @@ -812,6 +812,8 @@ impl Default for Chart { struct ChartsDemo { chart: Chart, vertical: bool, + allow_zoom: AxisBools, + allow_drag: AxisBools, } impl Default for ChartsDemo { @@ -819,22 +821,44 @@ impl Default for ChartsDemo { Self { vertical: true, chart: Chart::default(), + allow_zoom: true.into(), + allow_drag: true.into(), } } } impl ChartsDemo { fn ui(&mut self, ui: &mut Ui) -> Response { - ui.label("Type:"); ui.horizontal(|ui| { - ui.selectable_value(&mut self.chart, Chart::GaussBars, "Histogram"); - ui.selectable_value(&mut self.chart, Chart::StackedBars, "Stacked Bar Chart"); - ui.selectable_value(&mut self.chart, Chart::BoxPlot, "Box Plot"); - }); - ui.label("Orientation:"); - ui.horizontal(|ui| { - ui.selectable_value(&mut self.vertical, true, "Vertical"); - ui.selectable_value(&mut self.vertical, false, "Horizontal"); + ui.vertical(|ui| { + ui.label("Type:"); + ui.horizontal(|ui| { + ui.selectable_value(&mut self.chart, Chart::GaussBars, "Histogram"); + ui.selectable_value(&mut self.chart, Chart::StackedBars, "Stacked Bar Chart"); + ui.selectable_value(&mut self.chart, Chart::BoxPlot, "Box Plot"); + }); + ui.label("Orientation:"); + ui.horizontal(|ui| { + ui.selectable_value(&mut self.vertical, true, "Vertical"); + ui.selectable_value(&mut self.vertical, false, "Horizontal"); + }); + }); + ui.vertical(|ui| { + ui.group(|ui| { + ui.add_enabled_ui(self.chart != Chart::StackedBars, |ui| { + ui.horizontal(|ui| { + ui.label("Allow zoom:"); + ui.checkbox(&mut self.allow_zoom.x, "X"); + ui.checkbox(&mut self.allow_zoom.y, "Y"); + }); + }); + ui.horizontal(|ui| { + ui.label("Allow drag:"); + ui.checkbox(&mut self.allow_drag.x, "X"); + ui.checkbox(&mut self.allow_drag.y, "Y"); + }); + }); + }); }); match self.chart { Chart::GaussBars => self.bar_gauss(ui), @@ -867,6 +891,8 @@ impl ChartsDemo { Plot::new("Normal Distribution Demo") .legend(Legend::default()) .clamp_grid(true) + .allow_zoom(self.allow_zoom) + .allow_drag(self.allow_drag) .show(ui, |plot_ui| plot_ui.bar_chart(chart)) .response } @@ -925,6 +951,7 @@ impl ChartsDemo { Plot::new("Stacked Bar Chart Demo") .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); @@ -968,6 +995,8 @@ impl ChartsDemo { Plot::new("Box Plot Demo") .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);