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 <emil.ernerfeldt@gmail.com> * Simplify allow_drag and allow_zoom APIs to take in Into<AxisBools> * Remove unnecessary using... * Remove unrelated change --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
902bcfe6aa
commit
438f6eafc8
|
|
@ -7,7 +7,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Add `char_limit` to `TextEdit` singleline mode to limit the amount of characters
|
* 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)).
|
* ⚠️ 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
|
## 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)):
|
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
|
||||||
|
|
|
||||||
|
|
@ -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
|
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))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
struct AxisBools {
|
pub struct AxisBools {
|
||||||
x: bool,
|
pub x: bool,
|
||||||
y: bool,
|
pub y: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AxisBools {
|
impl AxisBools {
|
||||||
|
pub fn new(x: bool, y: bool) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn any(&self) -> bool {
|
pub fn any(&self) -> bool {
|
||||||
self.x || self.y
|
self.x || self.y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,8 +169,8 @@ pub struct Plot {
|
||||||
|
|
||||||
center_x_axis: bool,
|
center_x_axis: bool,
|
||||||
center_y_axis: bool,
|
center_y_axis: bool,
|
||||||
allow_zoom: bool,
|
allow_zoom: AxisBools,
|
||||||
allow_drag: bool,
|
allow_drag: AxisBools,
|
||||||
allow_scroll: bool,
|
allow_scroll: bool,
|
||||||
allow_double_click_reset: bool,
|
allow_double_click_reset: bool,
|
||||||
allow_boxed_zoom: bool,
|
allow_boxed_zoom: bool,
|
||||||
|
|
@ -207,8 +211,8 @@ impl Plot {
|
||||||
|
|
||||||
center_x_axis: false,
|
center_x_axis: false,
|
||||||
center_y_axis: false,
|
center_y_axis: false,
|
||||||
allow_zoom: true,
|
allow_zoom: true.into(),
|
||||||
allow_drag: true,
|
allow_drag: true.into(),
|
||||||
allow_scroll: true,
|
allow_scroll: true,
|
||||||
allow_double_click_reset: true,
|
allow_double_click_reset: true,
|
||||||
allow_boxed_zoom: true,
|
allow_boxed_zoom: true,
|
||||||
|
|
@ -305,8 +309,13 @@ impl Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to allow zooming in the plot. Default: `true`.
|
/// 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<T>(mut self, on: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<AxisBools>,
|
||||||
|
{
|
||||||
|
self.allow_zoom = on.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,8 +355,11 @@ impl Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to allow dragging in the plot to move the bounds. Default: `true`.
|
/// Whether to allow dragging in the plot to move the bounds. Default: `true`.
|
||||||
pub fn allow_drag(mut self, on: bool) -> Self {
|
pub fn allow_drag<T>(mut self, on: T) -> Self
|
||||||
self.allow_drag = on;
|
where
|
||||||
|
T: Into<AxisBools>,
|
||||||
|
{
|
||||||
|
self.allow_drag = on.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -829,9 +841,16 @@ impl Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dragging
|
// 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);
|
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();
|
bounds_modified = true.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -889,12 +908,18 @@ impl Plot {
|
||||||
|
|
||||||
let hover_pos = response.hover_pos();
|
let hover_pos = response.hover_pos();
|
||||||
if let Some(hover_pos) = hover_pos {
|
if let Some(hover_pos) = hover_pos {
|
||||||
if allow_zoom {
|
if allow_zoom.any() {
|
||||||
let zoom_factor = if data_aspect.is_some() {
|
let mut zoom_factor = if data_aspect.is_some() {
|
||||||
Vec2::splat(ui.input(|i| i.zoom_delta()))
|
Vec2::splat(ui.input(|i| i.zoom_delta()))
|
||||||
} else {
|
} else {
|
||||||
ui.input(|i| i.zoom_delta_2d())
|
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) {
|
if zoom_factor != Vec2::splat(1.0) {
|
||||||
transform.zoom(zoom_factor, hover_pos);
|
transform.zoom(zoom_factor, hover_pos);
|
||||||
bounds_modified = true.into();
|
bounds_modified = true.into();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use egui::plot::{GridInput, GridMark};
|
use egui::plot::{AxisBools, GridInput, GridMark};
|
||||||
use egui::*;
|
use egui::*;
|
||||||
use plot::{
|
use plot::{
|
||||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine,
|
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine,
|
||||||
|
|
@ -812,6 +812,8 @@ impl Default for Chart {
|
||||||
struct ChartsDemo {
|
struct ChartsDemo {
|
||||||
chart: Chart,
|
chart: Chart,
|
||||||
vertical: bool,
|
vertical: bool,
|
||||||
|
allow_zoom: AxisBools,
|
||||||
|
allow_drag: AxisBools,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChartsDemo {
|
impl Default for ChartsDemo {
|
||||||
|
|
@ -819,22 +821,44 @@ impl Default for ChartsDemo {
|
||||||
Self {
|
Self {
|
||||||
vertical: true,
|
vertical: true,
|
||||||
chart: Chart::default(),
|
chart: Chart::default(),
|
||||||
|
allow_zoom: true.into(),
|
||||||
|
allow_drag: true.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChartsDemo {
|
impl ChartsDemo {
|
||||||
fn ui(&mut self, ui: &mut Ui) -> Response {
|
fn ui(&mut self, ui: &mut Ui) -> Response {
|
||||||
ui.label("Type:");
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.selectable_value(&mut self.chart, Chart::GaussBars, "Histogram");
|
ui.vertical(|ui| {
|
||||||
ui.selectable_value(&mut self.chart, Chart::StackedBars, "Stacked Bar Chart");
|
ui.label("Type:");
|
||||||
ui.selectable_value(&mut self.chart, Chart::BoxPlot, "Box Plot");
|
ui.horizontal(|ui| {
|
||||||
});
|
ui.selectable_value(&mut self.chart, Chart::GaussBars, "Histogram");
|
||||||
ui.label("Orientation:");
|
ui.selectable_value(&mut self.chart, Chart::StackedBars, "Stacked Bar Chart");
|
||||||
ui.horizontal(|ui| {
|
ui.selectable_value(&mut self.chart, Chart::BoxPlot, "Box Plot");
|
||||||
ui.selectable_value(&mut self.vertical, true, "Vertical");
|
});
|
||||||
ui.selectable_value(&mut self.vertical, false, "Horizontal");
|
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 {
|
match self.chart {
|
||||||
Chart::GaussBars => self.bar_gauss(ui),
|
Chart::GaussBars => self.bar_gauss(ui),
|
||||||
|
|
@ -867,6 +891,8 @@ impl ChartsDemo {
|
||||||
Plot::new("Normal Distribution Demo")
|
Plot::new("Normal Distribution Demo")
|
||||||
.legend(Legend::default())
|
.legend(Legend::default())
|
||||||
.clamp_grid(true)
|
.clamp_grid(true)
|
||||||
|
.allow_zoom(self.allow_zoom)
|
||||||
|
.allow_drag(self.allow_drag)
|
||||||
.show(ui, |plot_ui| plot_ui.bar_chart(chart))
|
.show(ui, |plot_ui| plot_ui.bar_chart(chart))
|
||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
|
|
@ -925,6 +951,7 @@ impl ChartsDemo {
|
||||||
Plot::new("Stacked Bar Chart Demo")
|
Plot::new("Stacked Bar Chart Demo")
|
||||||
.legend(Legend::default())
|
.legend(Legend::default())
|
||||||
.data_aspect(1.0)
|
.data_aspect(1.0)
|
||||||
|
.allow_drag(self.allow_drag)
|
||||||
.show(ui, |plot_ui| {
|
.show(ui, |plot_ui| {
|
||||||
plot_ui.bar_chart(chart1);
|
plot_ui.bar_chart(chart1);
|
||||||
plot_ui.bar_chart(chart2);
|
plot_ui.bar_chart(chart2);
|
||||||
|
|
@ -968,6 +995,8 @@ impl ChartsDemo {
|
||||||
|
|
||||||
Plot::new("Box Plot Demo")
|
Plot::new("Box Plot Demo")
|
||||||
.legend(Legend::default())
|
.legend(Legend::default())
|
||||||
|
.allow_zoom(self.allow_zoom)
|
||||||
|
.allow_drag(self.allow_drag)
|
||||||
.show(ui, |plot_ui| {
|
.show(ui, |plot_ui| {
|
||||||
plot_ui.box_plot(box1);
|
plot_ui.box_plot(box1);
|
||||||
plot_ui.box_plot(box2);
|
plot_ui.box_plot(box2);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue