Plot boxed zoom with secondary mouse button (#1188)
This commit is contained in:
parent
c6ac1827f6
commit
869d556335
4 changed files with 83 additions and 0 deletions
|
@ -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 🔧
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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.;
|
||||||
|
|
|
@ -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…
Reference in a new issue