Improved plot interaction methods (#892)
ctx plot_bounds plot_hovered screen_from_plot plot_from_screen etc
This commit is contained in:
parent
6b5c4b9aec
commit
9d56bce592
7 changed files with 191 additions and 138 deletions
|
@ -12,7 +12,9 @@ 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)).
|
* 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)).
|
* 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)).
|
* 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)).
|
* You can now query information about the plot (e.g. get the mouse position in plot coordinates, or the plot
|
||||||
|
bounds) while adding items. `Plot` ([#766](https://github.com/emilk/egui/pull/766) and
|
||||||
|
[#892](https://github.com/emilk/egui/pull/892)).
|
||||||
* Add vertical sliders with `Slider::new(…).vertical()` ([#875](https://github.com/emilk/egui/pull/875)).
|
* Add vertical sliders with `Slider::new(…).vertical()` ([#875](https://github.com/emilk/egui/pull/875)).
|
||||||
* Add `Button::image_and_text` ([#832](https://github.com/emilk/egui/pull/832)).
|
* Add `Button::image_and_text` ([#832](https://github.com/emilk/egui/pull/832)).
|
||||||
|
|
||||||
|
@ -36,7 +38,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
* [5225225](https://github.com/5225225): ([#849](https://github.com/emilk/egui/pull/849)).
|
* [5225225](https://github.com/5225225): ([#849](https://github.com/emilk/egui/pull/849)).
|
||||||
* [B-Reif](https://github.com/B-Reif) ([#875](https://github.com/emilk/egui/pull/875)).
|
* [B-Reif](https://github.com/B-Reif) ([#875](https://github.com/emilk/egui/pull/875)).
|
||||||
* [d10sfan](https://github.com/d10sfan) ([#832](https://github.com/emilk/egui/pull/832)).
|
* [d10sfan](https://github.com/d10sfan) ([#832](https://github.com/emilk/egui/pull/832)).
|
||||||
* [EmbersArc](https://github.com/EmbersArc): ([#766](https://github.com/emilk/egui/pull/766)).
|
* [EmbersArc](https://github.com/EmbersArc): ([#766](https://github.com/emilk/egui/pull/766), [#892](https://github.com/emilk/egui/pull/892)).
|
||||||
* [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543)).
|
* [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543)).
|
||||||
* [sumibi-yakitori](https://github.com/sumibi-yakitori) ([#830](https://github.com/emilk/egui/pull/830)).
|
* [sumibi-yakitori](https://github.com/sumibi-yakitori) ([#830](https://github.com/emilk/egui/pull/830)).
|
||||||
* [t18b219k](https://github.com/t18b219k): ([#868](https://github.com/emilk/egui/pull/868), [#888](https://github.com/emilk/egui/pull/888)).
|
* [t18b219k](https://github.com/t18b219k): ([#868](https://github.com/emilk/egui/pull/868), [#888](https://github.com/emilk/egui/pull/888)).
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::ops::{Bound, RangeBounds, RangeInclusive};
|
||||||
|
|
||||||
use epaint::Mesh;
|
use epaint::Mesh;
|
||||||
|
|
||||||
use super::transform::{Bounds, ScreenTransform};
|
use super::transform::{PlotBounds, ScreenTransform};
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
const DEFAULT_FILL_ALPHA: f32 = 0.05;
|
const DEFAULT_FILL_ALPHA: f32 = 0.05;
|
||||||
|
@ -233,8 +233,8 @@ impl PlotItem for HLine {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = Bounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
bounds.min[1] = self.y;
|
bounds.min[1] = self.y;
|
||||||
bounds.max[1] = self.y;
|
bounds.max[1] = self.y;
|
||||||
bounds
|
bounds
|
||||||
|
@ -343,8 +343,8 @@ impl PlotItem for VLine {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = Bounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
bounds.min[0] = self.x;
|
bounds.min[0] = self.x;
|
||||||
bounds.max[0] = self.x;
|
bounds.max[0] = self.x;
|
||||||
bounds
|
bounds
|
||||||
|
@ -360,7 +360,7 @@ pub(super) trait PlotItem {
|
||||||
fn highlight(&mut self);
|
fn highlight(&mut self);
|
||||||
fn highlighted(&self) -> bool;
|
fn highlighted(&self) -> bool;
|
||||||
fn values(&self) -> Option<&Values>;
|
fn values(&self) -> Option<&Values>;
|
||||||
fn get_bounds(&self) -> Bounds;
|
fn get_bounds(&self) -> PlotBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -503,8 +503,8 @@ impl Values {
|
||||||
(start < end).then(|| start..=end)
|
(start < end).then(|| start..=end)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_bounds(&self) -> Bounds {
|
pub(super) fn get_bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = Bounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
self.values
|
self.values
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|value| bounds.extend_with(value));
|
.for_each(|value| bounds.extend_with(value));
|
||||||
|
@ -710,7 +710,7 @@ impl PlotItem for Line {
|
||||||
Some(&self.series)
|
Some(&self.series)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
self.series.get_bounds()
|
self.series.get_bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -840,7 +840,7 @@ impl PlotItem for Polygon {
|
||||||
Some(&self.series)
|
Some(&self.series)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
self.series.get_bounds()
|
self.series.get_bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -953,8 +953,8 @@ impl PlotItem for Text {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = Bounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
bounds.extend_with(&self.position);
|
bounds.extend_with(&self.position);
|
||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|
@ -1186,7 +1186,7 @@ impl PlotItem for Points {
|
||||||
Some(&self.series)
|
Some(&self.series)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
self.series.get_bounds()
|
self.series.get_bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1301,7 +1301,7 @@ impl PlotItem for Arrows {
|
||||||
Some(&self.origins)
|
Some(&self.origins)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
self.origins.get_bounds()
|
self.origins.get_bounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1431,8 +1431,8 @@ impl PlotItem for PlotImage {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self) -> Bounds {
|
fn get_bounds(&self) -> PlotBounds {
|
||||||
let mut bounds = Bounds::NOTHING;
|
let mut bounds = PlotBounds::NOTHING;
|
||||||
let left_top = Value::new(
|
let left_top = Value::new(
|
||||||
self.position.x as f32 - self.size.x / 2.0,
|
self.position.x as f32 - self.size.x / 2.0,
|
||||||
self.position.y as f32 - self.size.y / 2.0,
|
self.position.y as f32 - self.size.y / 2.0,
|
||||||
|
|
|
@ -11,7 +11,8 @@ pub use items::{
|
||||||
};
|
};
|
||||||
use legend::LegendWidget;
|
use legend::LegendWidget;
|
||||||
pub use legend::{Corner, Legend};
|
pub use legend::{Corner, Legend};
|
||||||
use transform::{Bounds, ScreenTransform};
|
pub use transform::PlotBounds;
|
||||||
|
use transform::ScreenTransform;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use color::Hsva;
|
use color::Hsva;
|
||||||
|
@ -23,12 +24,11 @@ use epaint::ahash::AHashSet;
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct PlotMemory {
|
struct PlotMemory {
|
||||||
bounds: Bounds,
|
|
||||||
auto_bounds: bool,
|
auto_bounds: bool,
|
||||||
hovered_entry: Option<String>,
|
hovered_entry: Option<String>,
|
||||||
hidden_items: AHashSet<String>,
|
hidden_items: AHashSet<String>,
|
||||||
min_auto_bounds: Bounds,
|
min_auto_bounds: PlotBounds,
|
||||||
last_screen_transform: Option<ScreenTransform>,
|
last_screen_transform: ScreenTransform,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotMemory {
|
impl PlotMemory {
|
||||||
|
@ -65,7 +65,7 @@ pub struct Plot {
|
||||||
center_y_axis: bool,
|
center_y_axis: bool,
|
||||||
allow_zoom: bool,
|
allow_zoom: bool,
|
||||||
allow_drag: bool,
|
allow_drag: bool,
|
||||||
min_auto_bounds: Bounds,
|
min_auto_bounds: PlotBounds,
|
||||||
margin_fraction: Vec2,
|
margin_fraction: Vec2,
|
||||||
|
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
|
@ -91,7 +91,7 @@ impl Plot {
|
||||||
center_y_axis: false,
|
center_y_axis: false,
|
||||||
allow_zoom: true,
|
allow_zoom: true,
|
||||||
allow_drag: true,
|
allow_drag: true,
|
||||||
min_auto_bounds: Bounds::NOTHING,
|
min_auto_bounds: PlotBounds::NOTHING,
|
||||||
margin_fraction: Vec2::splat(0.05),
|
margin_fraction: Vec2::splat(0.05),
|
||||||
|
|
||||||
min_size: Vec2::splat(64.0),
|
min_size: Vec2::splat(64.0),
|
||||||
|
@ -219,7 +219,7 @@ impl Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interact with and add items to the plot and finally draw it.
|
/// 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 {
|
pub fn show<R>(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> InnerResponse<R> {
|
||||||
let Self {
|
let Self {
|
||||||
id_source,
|
id_source,
|
||||||
center_x_axis,
|
center_x_axis,
|
||||||
|
@ -240,37 +240,6 @@ impl Plot {
|
||||||
show_axes,
|
show_axes,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let plot_id = ui.make_persistent_id(id_source);
|
|
||||||
let mut memory = PlotMemory::load(ui.ctx(), plot_id).unwrap_or_else(|| PlotMemory {
|
|
||||||
bounds: min_auto_bounds,
|
|
||||||
auto_bounds: !min_auto_bounds.is_valid(),
|
|
||||||
hovered_entry: None,
|
|
||||||
hidden_items: Default::default(),
|
|
||||||
min_auto_bounds,
|
|
||||||
last_screen_transform: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the min bounds changed, recalculate everything.
|
|
||||||
if min_auto_bounds != memory.min_auto_bounds {
|
|
||||||
memory = PlotMemory {
|
|
||||||
bounds: min_auto_bounds,
|
|
||||||
auto_bounds: !min_auto_bounds.is_valid(),
|
|
||||||
hovered_entry: None,
|
|
||||||
min_auto_bounds,
|
|
||||||
..memory
|
|
||||||
};
|
|
||||||
memory.clone().store(ui.ctx(), plot_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let PlotMemory {
|
|
||||||
mut bounds,
|
|
||||||
mut auto_bounds,
|
|
||||||
mut hovered_entry,
|
|
||||||
mut hidden_items,
|
|
||||||
last_screen_transform,
|
|
||||||
..
|
|
||||||
} = memory;
|
|
||||||
|
|
||||||
// Determine the size of the plot in the UI
|
// Determine the size of the plot in the UI
|
||||||
let size = {
|
let size = {
|
||||||
let width = width
|
let width = width
|
||||||
|
@ -295,8 +264,42 @@ impl Plot {
|
||||||
vec2(width, height)
|
vec2(width, height)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Allocate the space.
|
||||||
let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
|
let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
|
||||||
let plot_painter = ui.painter().sub_region(rect);
|
|
||||||
|
// Load or initialize the memory.
|
||||||
|
let plot_id = ui.make_persistent_id(id_source);
|
||||||
|
let mut memory = PlotMemory::load(ui.ctx(), plot_id).unwrap_or_else(|| PlotMemory {
|
||||||
|
auto_bounds: !min_auto_bounds.is_valid(),
|
||||||
|
hovered_entry: None,
|
||||||
|
hidden_items: Default::default(),
|
||||||
|
min_auto_bounds,
|
||||||
|
last_screen_transform: ScreenTransform::new(
|
||||||
|
rect,
|
||||||
|
min_auto_bounds,
|
||||||
|
center_x_axis,
|
||||||
|
center_y_axis,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the min bounds changed, recalculate everything.
|
||||||
|
if min_auto_bounds != memory.min_auto_bounds {
|
||||||
|
memory = PlotMemory {
|
||||||
|
auto_bounds: !min_auto_bounds.is_valid(),
|
||||||
|
hovered_entry: None,
|
||||||
|
min_auto_bounds,
|
||||||
|
..memory
|
||||||
|
};
|
||||||
|
memory.clone().store(ui.ctx(), plot_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let PlotMemory {
|
||||||
|
mut auto_bounds,
|
||||||
|
mut hovered_entry,
|
||||||
|
mut hidden_items,
|
||||||
|
last_screen_transform,
|
||||||
|
..
|
||||||
|
} = memory;
|
||||||
|
|
||||||
// Call the plot build function.
|
// Call the plot build function.
|
||||||
let mut plot_ui = PlotUi {
|
let mut plot_ui = PlotUi {
|
||||||
|
@ -304,17 +307,19 @@ impl Plot {
|
||||||
next_auto_color_idx: 0,
|
next_auto_color_idx: 0,
|
||||||
last_screen_transform,
|
last_screen_transform,
|
||||||
response,
|
response,
|
||||||
|
ctx: ui.ctx().clone(),
|
||||||
};
|
};
|
||||||
build_fn(&mut plot_ui);
|
let inner = build_fn(&mut plot_ui);
|
||||||
let PlotUi {
|
let PlotUi {
|
||||||
mut items,
|
mut items,
|
||||||
response,
|
mut response,
|
||||||
|
last_screen_transform,
|
||||||
..
|
..
|
||||||
} = plot_ui;
|
} = plot_ui;
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
if show_background {
|
if show_background {
|
||||||
plot_painter.add(epaint::RectShape {
|
ui.painter().sub_region(rect).add(epaint::RectShape {
|
||||||
rect,
|
rect,
|
||||||
corner_radius: 2.0,
|
corner_radius: 2.0,
|
||||||
fill: ui.visuals().extreme_bg_color,
|
fill: ui.visuals().extreme_bg_color,
|
||||||
|
@ -322,7 +327,7 @@ impl Plot {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legend
|
// --- Legend ---
|
||||||
let legend = legend_config
|
let legend = legend_config
|
||||||
.and_then(|config| LegendWidget::try_new(rect, config, &items, &hidden_items));
|
.and_then(|config| LegendWidget::try_new(rect, config, &items, &hidden_items));
|
||||||
// Don't show hover cursor when hovering over legend.
|
// Don't show hover cursor when hovering over legend.
|
||||||
|
@ -342,6 +347,9 @@ impl Plot {
|
||||||
// Move highlighted items to front.
|
// Move highlighted items to front.
|
||||||
items.sort_by_key(|item| item.highlighted());
|
items.sort_by_key(|item| item.highlighted());
|
||||||
|
|
||||||
|
// --- Bound computation ---
|
||||||
|
let mut bounds = *last_screen_transform.bounds();
|
||||||
|
|
||||||
// Allow double clicking to reset to automatic bounds.
|
// Allow double clicking to reset to automatic bounds.
|
||||||
auto_bounds |= response.double_clicked_by(PointerButton::Primary);
|
auto_bounds |= response.double_clicked_by(PointerButton::Primary);
|
||||||
|
|
||||||
|
@ -363,6 +371,7 @@ impl Plot {
|
||||||
|
|
||||||
// Dragging
|
// Dragging
|
||||||
if allow_drag && response.dragged_by(PointerButton::Primary) {
|
if allow_drag && response.dragged_by(PointerButton::Primary) {
|
||||||
|
response = response.on_hover_cursor(CursorIcon::Grabbing);
|
||||||
transform.translate_bounds(-response.drag_delta());
|
transform.translate_bounds(-response.drag_delta());
|
||||||
auto_bounds = false;
|
auto_bounds = false;
|
||||||
}
|
}
|
||||||
|
@ -393,8 +402,6 @@ impl Plot {
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|item| item.initialize(transform.bounds().range_x()));
|
.for_each(|item| item.initialize(transform.bounds().range_x()));
|
||||||
|
|
||||||
let bounds = *transform.bounds();
|
|
||||||
|
|
||||||
let prepared = PreparedPlot {
|
let prepared = PreparedPlot {
|
||||||
items,
|
items,
|
||||||
show_x,
|
show_x,
|
||||||
|
@ -411,20 +418,21 @@ impl Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
let memory = PlotMemory {
|
let memory = PlotMemory {
|
||||||
bounds,
|
|
||||||
auto_bounds,
|
auto_bounds,
|
||||||
hovered_entry,
|
hovered_entry,
|
||||||
hidden_items,
|
hidden_items,
|
||||||
min_auto_bounds,
|
min_auto_bounds,
|
||||||
last_screen_transform: Some(transform),
|
last_screen_transform: transform,
|
||||||
};
|
};
|
||||||
memory.store(ui.ctx(), plot_id);
|
memory.store(ui.ctx(), plot_id);
|
||||||
|
|
||||||
if show_x || show_y {
|
let response = if show_x || show_y {
|
||||||
response.on_hover_cursor(CursorIcon::Crosshair)
|
response.on_hover_cursor(CursorIcon::Crosshair)
|
||||||
} else {
|
} else {
|
||||||
response
|
response
|
||||||
}
|
};
|
||||||
|
|
||||||
|
InnerResponse { inner, response }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,8 +441,9 @@ impl Plot {
|
||||||
pub struct PlotUi {
|
pub struct PlotUi {
|
||||||
items: Vec<Box<dyn PlotItem>>,
|
items: Vec<Box<dyn PlotItem>>,
|
||||||
next_auto_color_idx: usize,
|
next_auto_color_idx: usize,
|
||||||
last_screen_transform: Option<ScreenTransform>,
|
last_screen_transform: ScreenTransform,
|
||||||
response: Response,
|
response: Response,
|
||||||
|
ctx: CtxRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotUi {
|
impl PlotUi {
|
||||||
|
@ -446,35 +455,45 @@ impl PlotUi {
|
||||||
Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO: OkLab or some other perspective color space
|
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 ctx(&self) -> &CtxRef {
|
||||||
|
&self.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The plot bounds as they were in the last frame. If called on the first frame and the bounds were not
|
||||||
|
/// further specified in the plot builder, this will return bounds centered on the origin. The bounds do
|
||||||
|
/// not change until the plot is drawn.
|
||||||
|
pub fn plot_bounds(&self) -> PlotBounds {
|
||||||
|
*self.last_screen_transform.bounds()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the plot area is currently hovered.
|
||||||
|
pub fn plot_hovered(&self) -> bool {
|
||||||
|
self.response.hovered()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area.
|
||||||
pub fn pointer_coordinate(&self) -> Option<Value> {
|
pub fn pointer_coordinate(&self) -> Option<Value> {
|
||||||
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:
|
// 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 last_pos = self.ctx().input().pointer.latest_pos()? - self.response.drag_delta();
|
||||||
let value = last_screen_transform.value_from_position(last_pos);
|
let value = self.plot_from_screen(last_pos);
|
||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pointer drag delta in plot coordinates.
|
/// The pointer drag delta in plot coordinates.
|
||||||
pub fn pointer_coordinate_drag_delta(&self) -> Vec2 {
|
pub fn pointer_coordinate_drag_delta(&self) -> Vec2 {
|
||||||
self.last_screen_transform
|
let delta = self.response.drag_delta();
|
||||||
.as_ref()
|
let dp_dv = self.last_screen_transform.dpos_dvalue();
|
||||||
.map_or(Vec2::ZERO, |tf| {
|
Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32)
|
||||||
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 plot coordinates to screen coordinates.
|
||||||
})
|
pub fn screen_from_plot(&self, position: Value) -> Pos2 {
|
||||||
|
self.last_screen_transform.position_from_value(&position)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform the screen coordinates to plot coordinates.
|
/// Transform the screen coordinates to plot coordinates.
|
||||||
pub fn plot_from_screen(&self, position: Pos2) -> Pos2 {
|
pub fn plot_from_screen(&self, position: Pos2) -> Value {
|
||||||
self.last_screen_transform
|
self.last_screen_transform.value_from_position(position)
|
||||||
.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.
|
/// Add a data line.
|
||||||
|
|
|
@ -7,18 +7,26 @@ use crate::*;
|
||||||
/// The range of data values we show.
|
/// The range of data values we show.
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub(crate) struct Bounds {
|
pub struct PlotBounds {
|
||||||
pub min: [f64; 2],
|
pub(crate) min: [f64; 2],
|
||||||
pub max: [f64; 2],
|
pub(crate) max: [f64; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bounds {
|
impl PlotBounds {
|
||||||
pub const NOTHING: Self = Self {
|
pub const NOTHING: Self = Self {
|
||||||
min: [f64::INFINITY; 2],
|
min: [f64::INFINITY; 2],
|
||||||
max: [-f64::INFINITY; 2],
|
max: [-f64::INFINITY; 2],
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn new_symmetrical(half_extent: f64) -> Self {
|
pub fn min(&self) -> [f64; 2] {
|
||||||
|
self.min
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(&self) -> [f64; 2] {
|
||||||
|
self.max
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_symmetrical(half_extent: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
min: [-half_extent; 2],
|
min: [-half_extent; 2],
|
||||||
max: [half_extent; 2],
|
max: [half_extent; 2],
|
||||||
|
@ -44,73 +52,73 @@ impl Bounds {
|
||||||
self.max[1] - self.min[1]
|
self.max[1] - self.min[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend_with(&mut self, value: &Value) {
|
pub(crate) fn extend_with(&mut self, value: &Value) {
|
||||||
self.extend_with_x(value.x);
|
self.extend_with_x(value.x);
|
||||||
self.extend_with_y(value.y);
|
self.extend_with_y(value.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand to include the given x coordinate
|
/// Expand to include the given x coordinate
|
||||||
pub fn extend_with_x(&mut self, x: f64) {
|
pub(crate) fn extend_with_x(&mut self, x: f64) {
|
||||||
self.min[0] = self.min[0].min(x);
|
self.min[0] = self.min[0].min(x);
|
||||||
self.max[0] = self.max[0].max(x);
|
self.max[0] = self.max[0].max(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand to include the given y coordinate
|
/// Expand to include the given y coordinate
|
||||||
pub fn extend_with_y(&mut self, y: f64) {
|
pub(crate) fn extend_with_y(&mut self, y: f64) {
|
||||||
self.min[1] = self.min[1].min(y);
|
self.min[1] = self.min[1].min(y);
|
||||||
self.max[1] = self.max[1].max(y);
|
self.max[1] = self.max[1].max(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_x(&mut self, pad: f64) {
|
pub(crate) fn expand_x(&mut self, pad: f64) {
|
||||||
self.min[0] -= pad;
|
self.min[0] -= pad;
|
||||||
self.max[0] += pad;
|
self.max[0] += pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_y(&mut self, pad: f64) {
|
pub(crate) fn expand_y(&mut self, pad: f64) {
|
||||||
self.min[1] -= pad;
|
self.min[1] -= pad;
|
||||||
self.max[1] += pad;
|
self.max[1] += pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge(&mut self, other: &Bounds) {
|
pub(crate) fn merge(&mut self, other: &PlotBounds) {
|
||||||
self.min[0] = self.min[0].min(other.min[0]);
|
self.min[0] = self.min[0].min(other.min[0]);
|
||||||
self.min[1] = self.min[1].min(other.min[1]);
|
self.min[1] = self.min[1].min(other.min[1]);
|
||||||
self.max[0] = self.max[0].max(other.max[0]);
|
self.max[0] = self.max[0].max(other.max[0]);
|
||||||
self.max[1] = self.max[1].max(other.max[1]);
|
self.max[1] = self.max[1].max(other.max[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn translate_x(&mut self, delta: f64) {
|
pub(crate) fn translate_x(&mut self, delta: f64) {
|
||||||
self.min[0] += delta;
|
self.min[0] += delta;
|
||||||
self.max[0] += delta;
|
self.max[0] += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn translate_y(&mut self, delta: f64) {
|
pub(crate) fn translate_y(&mut self, delta: f64) {
|
||||||
self.min[1] += delta;
|
self.min[1] += delta;
|
||||||
self.max[1] += delta;
|
self.max[1] += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn translate(&mut self, delta: Vec2) {
|
pub(crate) fn translate(&mut self, delta: Vec2) {
|
||||||
self.translate_x(delta.x as f64);
|
self.translate_x(delta.x as f64);
|
||||||
self.translate_y(delta.y as f64);
|
self.translate_y(delta.y as f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_relative_margin(&mut self, margin_fraction: Vec2) {
|
pub(crate) fn add_relative_margin(&mut self, margin_fraction: Vec2) {
|
||||||
let width = self.width().max(0.0);
|
let width = self.width().max(0.0);
|
||||||
let height = self.height().max(0.0);
|
let height = self.height().max(0.0);
|
||||||
self.expand_x(margin_fraction.x as f64 * width);
|
self.expand_x(margin_fraction.x as f64 * width);
|
||||||
self.expand_y(margin_fraction.y as f64 * height);
|
self.expand_y(margin_fraction.y as f64 * height);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn range_x(&self) -> RangeInclusive<f64> {
|
pub(crate) fn range_x(&self) -> RangeInclusive<f64> {
|
||||||
self.min[0]..=self.max[0]
|
self.min[0]..=self.max[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_x_symmetrical(&mut self) {
|
pub(crate) fn make_x_symmetrical(&mut self) {
|
||||||
let x_abs = self.min[0].abs().max(self.max[0].abs());
|
let x_abs = self.min[0].abs().max(self.max[0].abs());
|
||||||
self.min[0] = -x_abs;
|
self.min[0] = -x_abs;
|
||||||
self.max[0] = x_abs;
|
self.max[0] = x_abs;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_y_symmetrical(&mut self) {
|
pub(crate) fn make_y_symmetrical(&mut self) {
|
||||||
let y_abs = self.min[1].abs().max(self.max[1].abs());
|
let y_abs = self.min[1].abs().max(self.max[1].abs());
|
||||||
self.min[1] = -y_abs;
|
self.min[1] = -y_abs;
|
||||||
self.max[1] = y_abs;
|
self.max[1] = y_abs;
|
||||||
|
@ -124,7 +132,7 @@ pub(crate) struct ScreenTransform {
|
||||||
/// The screen rectangle.
|
/// The screen rectangle.
|
||||||
frame: Rect,
|
frame: Rect,
|
||||||
/// The plot bounds.
|
/// The plot bounds.
|
||||||
bounds: Bounds,
|
bounds: PlotBounds,
|
||||||
/// Whether to always center the x-range of the bounds.
|
/// Whether to always center the x-range of the bounds.
|
||||||
x_centered: bool,
|
x_centered: bool,
|
||||||
/// Whether to always center the y-range of the bounds.
|
/// Whether to always center the y-range of the bounds.
|
||||||
|
@ -132,10 +140,10 @@ pub(crate) struct ScreenTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScreenTransform {
|
impl ScreenTransform {
|
||||||
pub fn new(frame: Rect, mut bounds: Bounds, x_centered: bool, y_centered: bool) -> Self {
|
pub fn new(frame: Rect, mut bounds: PlotBounds, x_centered: bool, y_centered: bool) -> Self {
|
||||||
// Make sure they are not empty.
|
// Make sure they are not empty.
|
||||||
if !bounds.is_valid() {
|
if !bounds.is_valid() {
|
||||||
bounds = Bounds::new_symmetrical(1.0);
|
bounds = PlotBounds::new_symmetrical(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale axes so that the origin is in the center.
|
// Scale axes so that the origin is in the center.
|
||||||
|
@ -158,7 +166,7 @@ impl ScreenTransform {
|
||||||
&self.frame
|
&self.frame
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bounds(&self) -> &Bounds {
|
pub fn bounds(&self) -> &PlotBounds {
|
||||||
&self.bounds
|
&self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,7 @@ impl ContextMenus {
|
||||||
.height(self.height)
|
.height(self.height)
|
||||||
.data_aspect(1.0)
|
.data_aspect(1.0)
|
||||||
.show(ui, |plot_ui| plot_ui.line(line))
|
.show(ui, |plot_ui| plot_ui.line(line))
|
||||||
|
.response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nested_menus(ui: &mut egui::Ui) {
|
fn nested_menus(ui: &mut egui::Ui) {
|
||||||
|
|
|
@ -155,6 +155,7 @@ impl Widget for &mut LineDemo {
|
||||||
plot_ui.line(self.sin());
|
plot_ui.line(self.sin());
|
||||||
plot_ui.line(self.thingy());
|
plot_ui.line(self.thingy());
|
||||||
})
|
})
|
||||||
|
.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,11 +226,13 @@ impl Widget for &mut MarkerDemo {
|
||||||
let markers_plot = Plot::new("markers_demo")
|
let markers_plot = Plot::new("markers_demo")
|
||||||
.data_aspect(1.0)
|
.data_aspect(1.0)
|
||||||
.legend(Legend::default());
|
.legend(Legend::default());
|
||||||
markers_plot.show(ui, |plot_ui| {
|
markers_plot
|
||||||
for marker in self.markers() {
|
.show(ui, |plot_ui| {
|
||||||
plot_ui.points(marker);
|
for marker in self.markers() {
|
||||||
}
|
plot_ui.points(marker);
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,13 +292,15 @@ impl Widget for &mut LegendDemo {
|
||||||
});
|
});
|
||||||
|
|
||||||
let legend_plot = Plot::new("legend_demo").legend(*config).data_aspect(1.0);
|
let legend_plot = Plot::new("legend_demo").legend(*config).data_aspect(1.0);
|
||||||
legend_plot.show(ui, |plot_ui| {
|
legend_plot
|
||||||
plot_ui.line(LegendDemo::line_with_slope(0.5).name("lines"));
|
.show(ui, |plot_ui| {
|
||||||
plot_ui.line(LegendDemo::line_with_slope(1.0).name("lines"));
|
plot_ui.line(LegendDemo::line_with_slope(0.5).name("lines"));
|
||||||
plot_ui.line(LegendDemo::line_with_slope(2.0).name("lines"));
|
plot_ui.line(LegendDemo::line_with_slope(1.0).name("lines"));
|
||||||
plot_ui.line(LegendDemo::sin().name("sin(x)"));
|
plot_ui.line(LegendDemo::line_with_slope(2.0).name("lines"));
|
||||||
plot_ui.line(LegendDemo::cos().name("cos(x)"));
|
plot_ui.line(LegendDemo::sin().name("sin(x)"));
|
||||||
})
|
plot_ui.line(LegendDemo::cos().name("cos(x)"));
|
||||||
|
})
|
||||||
|
.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,45 +371,62 @@ impl Widget for &mut ItemsDemo {
|
||||||
plot_ui.image(image.name("Image"));
|
plot_ui.image(image.name("Image"));
|
||||||
plot_ui.arrows(arrows.name("Arrows"));
|
plot_ui.arrows(arrows.name("Arrows"));
|
||||||
})
|
})
|
||||||
|
.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
struct InteractionDemo {
|
struct InteractionDemo {}
|
||||||
pointer_coordinate: Option<Value>,
|
|
||||||
pointer_coordinate_drag_delta: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for InteractionDemo {
|
impl Default for InteractionDemo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {}
|
||||||
pointer_coordinate: None,
|
|
||||||
pointer_coordinate_drag_delta: Vec2::ZERO,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &mut InteractionDemo {
|
impl Widget for &mut InteractionDemo {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let coordinate_text = if let Some(coordinate) = self.pointer_coordinate {
|
let plot = Plot::new("interaction_demo").height(300.0);
|
||||||
format!("x: {:.03}, y: {:.03}", coordinate.x, coordinate.y)
|
|
||||||
|
let InnerResponse {
|
||||||
|
response,
|
||||||
|
inner: (screen_pos, pointer_coordinate, pointer_coordinate_drag_delta, bounds, hovered),
|
||||||
|
} = plot.show(ui, |plot_ui| {
|
||||||
|
(
|
||||||
|
plot_ui.screen_from_plot(Value::new(0.0, 0.0)),
|
||||||
|
plot_ui.pointer_coordinate(),
|
||||||
|
plot_ui.pointer_coordinate_drag_delta(),
|
||||||
|
plot_ui.plot_bounds(),
|
||||||
|
plot_ui.plot_hovered(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.label(format!(
|
||||||
|
"plot bounds: min: {:.02?}, max: {:.02?}",
|
||||||
|
bounds.min(),
|
||||||
|
bounds.max()
|
||||||
|
));
|
||||||
|
ui.label(format!(
|
||||||
|
"origin in screen coordinates: x: {:.02}, y: {:.02}",
|
||||||
|
screen_pos.x, screen_pos.y
|
||||||
|
));
|
||||||
|
ui.label(format!("plot hovered: {}", hovered));
|
||||||
|
let coordinate_text = if let Some(coordinate) = pointer_coordinate {
|
||||||
|
format!("x: {:.02}, y: {:.02}", coordinate.x, coordinate.y)
|
||||||
} else {
|
} else {
|
||||||
"None".to_string()
|
"None".to_string()
|
||||||
};
|
};
|
||||||
ui.label(format!("pointer coordinate: {}", coordinate_text));
|
ui.label(format!("pointer coordinate: {}", coordinate_text));
|
||||||
let coordinate_text = format!(
|
let coordinate_text = format!(
|
||||||
"x: {:.03}, y: {:.03}",
|
"x: {:.02}, y: {:.02}",
|
||||||
self.pointer_coordinate_drag_delta.x, self.pointer_coordinate_drag_delta.y
|
pointer_coordinate_drag_delta.x, pointer_coordinate_drag_delta.y
|
||||||
);
|
);
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"pointer coordinate drag delta: {}",
|
"pointer coordinate drag delta: {}",
|
||||||
coordinate_text
|
coordinate_text
|
||||||
));
|
));
|
||||||
let plot = Plot::new("interaction_demo");
|
|
||||||
plot.show(ui, |plot_ui| {
|
response
|
||||||
self.pointer_coordinate = plot_ui.pointer_coordinate();
|
|
||||||
self.pointer_coordinate_drag_delta = plot_ui.pointer_coordinate_drag_delta();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,7 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response {
|
||||||
.height(32.0)
|
.height(32.0)
|
||||||
.data_aspect(1.0)
|
.data_aspect(1.0)
|
||||||
.show(ui, |plot_ui| plot_ui.line(line))
|
.show(ui, |plot_ui| plot_ui.line(line))
|
||||||
|
.response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a {
|
fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a {
|
||||||
|
|
Loading…
Reference in a new issue