Browse Source

Plot boxed zoom with secondary mouse button (#1188)

pull/1045/head
nongiach 3 years ago
committed by GitHub
parent
commit
869d556335
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 77
      egui/src/widgets/plot/mod.rs
  3. 4
      egui/src/widgets/plot/transform.rs
  4. 1
      egui_demo_lib/src/apps/demo/plot_demo.rs

1
CHANGELOG.md

@ -19,6 +19,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
* Added `CollapsingHeader::icon` to override the default open/close icon using a custom function. ([1147](https://github.com/emilk/egui/pull/1147)). * Added `CollapsingHeader::icon` to override the default open/close icon using a custom function. ([1147](https://github.com/emilk/egui/pull/1147)).
* Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)). * Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)).
* Added `ui.data()`, `ctx.data()`, `ctx.options()` and `ctx.tessellation_options()` ([#1175](https://github.com/emilk/egui/pull/1175)). * Added `ui.data()`, `ctx.data()`, `ctx.options()` and `ctx.tessellation_options()` ([#1175](https://github.com/emilk/egui/pull/1175)).
* Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)).
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)). * Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
### Changed 🔧 ### Changed 🔧

77
egui/src/widgets/plot/mod.rs

@ -37,6 +37,8 @@ struct PlotMemory {
hidden_items: AHashSet<String>, hidden_items: AHashSet<String>,
min_auto_bounds: PlotBounds, min_auto_bounds: PlotBounds,
last_screen_transform: ScreenTransform, last_screen_transform: ScreenTransform,
/// Allows to remember the first click position when performing a boxed zoom
last_click_pos_for_zoom: Option<Pos2>,
} }
impl PlotMemory { impl PlotMemory {
@ -132,6 +134,8 @@ pub struct Plot {
allow_drag: bool, allow_drag: bool,
min_auto_bounds: PlotBounds, min_auto_bounds: PlotBounds,
margin_fraction: Vec2, margin_fraction: Vec2,
allow_boxed_zoom: bool,
boxed_zoom_pointer_button: PointerButton,
linked_axes: Option<LinkedAxisGroup>, linked_axes: Option<LinkedAxisGroup>,
min_size: Vec2, min_size: Vec2,
@ -161,6 +165,8 @@ impl Plot {
allow_drag: true, allow_drag: true,
min_auto_bounds: PlotBounds::NOTHING, min_auto_bounds: PlotBounds::NOTHING,
margin_fraction: Vec2::splat(0.05), margin_fraction: Vec2::splat(0.05),
allow_boxed_zoom: true,
boxed_zoom_pointer_button: PointerButton::Secondary,
linked_axes: None, linked_axes: None,
min_size: Vec2::splat(64.0), min_size: Vec2::splat(64.0),
@ -247,6 +253,20 @@ impl Plot {
self self
} }
/// Whether to allow zooming in the plot by dragging out a box with the secondary mouse button.
///
/// Default: `true`.
pub fn allow_boxed_zoom(mut self, on: bool) -> Self {
self.allow_boxed_zoom = on;
self
}
/// Config the button pointer to use for boxed zooming. Default: `Secondary`
pub fn boxed_zoom_pointer_button(mut self, boxed_zoom_pointer_button: PointerButton) -> Self {
self.boxed_zoom_pointer_button = boxed_zoom_pointer_button;
self
}
/// 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(mut self, on: bool) -> Self {
self.allow_drag = on; self.allow_drag = on;
@ -357,6 +377,8 @@ impl Plot {
center_y_axis, center_y_axis,
allow_zoom, allow_zoom,
allow_drag, allow_drag,
allow_boxed_zoom,
boxed_zoom_pointer_button: boxed_zoom_pointer,
min_auto_bounds, min_auto_bounds,
margin_fraction, margin_fraction,
width, width,
@ -414,6 +436,7 @@ impl Plot {
center_x_axis, center_x_axis,
center_y_axis, center_y_axis,
), ),
last_click_pos_for_zoom: None,
}); });
// If the min bounds changed, recalculate everything. // If the min bounds changed, recalculate everything.
@ -432,6 +455,7 @@ impl Plot {
mut hovered_entry, mut hovered_entry,
mut hidden_items, mut hidden_items,
last_screen_transform, last_screen_transform,
mut last_click_pos_for_zoom,
.. ..
} = memory; } = memory;
@ -530,6 +554,53 @@ impl Plot {
} }
// Zooming // Zooming
let mut boxed_zoom_rect = None;
if allow_boxed_zoom {
// Save last click to allow boxed zooming
if response.drag_started() && response.dragged_by(boxed_zoom_pointer) {
// it would be best for egui that input has a memory of the last click pos because it's a common pattern
last_click_pos_for_zoom = response.hover_pos();
}
let box_start_pos = last_click_pos_for_zoom;
let box_end_pos = response.hover_pos();
if let (Some(box_start_pos), Some(box_end_pos)) = (box_start_pos, box_end_pos) {
// while dragging prepare a Shape and draw it later on top of the plot
if response.dragged_by(boxed_zoom_pointer) {
response = response.on_hover_cursor(CursorIcon::ZoomIn);
let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos);
boxed_zoom_rect = Some((
epaint::RectShape::stroke(
rect,
0.0,
epaint::Stroke::new(4., Color32::DARK_BLUE),
), // Outer stroke
epaint::RectShape::stroke(
rect,
0.0,
epaint::Stroke::new(2., Color32::WHITE),
), // Inner stroke
));
}
// when the click is release perform the zoom
if response.drag_released() {
let box_start_pos = transform.value_from_position(box_start_pos);
let box_end_pos = transform.value_from_position(box_end_pos);
let new_bounds = PlotBounds {
min: [box_start_pos.x, box_end_pos.y],
max: [box_end_pos.x, box_start_pos.y],
};
if new_bounds.is_valid() {
*transform.bounds_mut() = new_bounds;
auto_bounds = false;
} else {
auto_bounds = true;
}
// reset the boxed zoom state
last_click_pos_for_zoom = None;
}
}
}
if allow_zoom { if allow_zoom {
if let Some(hover_pos) = response.hover_pos() { if let Some(hover_pos) = response.hover_pos() {
let zoom_factor = if data_aspect.is_some() { let zoom_factor = if data_aspect.is_some() {
@ -566,6 +637,11 @@ impl Plot {
}; };
prepared.ui(ui, &response); prepared.ui(ui, &response);
if let Some(boxed_zoom_rect) = boxed_zoom_rect {
ui.painter().sub_region(rect).add(boxed_zoom_rect.0);
ui.painter().sub_region(rect).add(boxed_zoom_rect.1);
}
if let Some(mut legend) = legend { if let Some(mut legend) = legend {
ui.add(&mut legend); ui.add(&mut legend);
hidden_items = legend.get_hidden_items(); hidden_items = legend.get_hidden_items();
@ -582,6 +658,7 @@ impl Plot {
hidden_items, hidden_items,
min_auto_bounds, min_auto_bounds,
last_screen_transform: transform, last_screen_transform: transform,
last_click_pos_for_zoom,
}; };
memory.store(ui.ctx(), plot_id); memory.store(ui.ctx(), plot_id);

4
egui/src/widgets/plot/transform.rs

@ -178,6 +178,10 @@ impl ScreenTransform {
&self.bounds &self.bounds
} }
pub fn bounds_mut(&mut self) -> &mut PlotBounds {
&mut self.bounds
}
pub fn translate_bounds(&mut self, mut delta_pos: Vec2) { pub fn translate_bounds(&mut self, mut delta_pos: Vec2) {
if self.x_centered { if self.x_centered {
delta_pos.x = 0.; delta_pos.x = 0.;

1
egui_demo_lib/src/apps/demo/plot_demo.rs

@ -753,6 +753,7 @@ impl super::View for PlotDemo {
egui::reset_button(ui, self); egui::reset_button(ui, self);
ui.collapsing("Instructions", |ui| { ui.collapsing("Instructions", |ui| {
ui.label("Pan by dragging, or scroll (+ shift = horizontal)."); ui.label("Pan by dragging, or scroll (+ shift = horizontal).");
ui.label("Box zooming: Right click to zoom in and zoom out using a selection.");
if cfg!(target_arch = "wasm32") { if cfg!(target_arch = "wasm32") {
ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture."); ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture.");
} else if cfg!(target_os = "macos") { } else if cfg!(target_os = "macos") {

Loading…
Cancel
Save