Improved plot groups and bounds handling (#2410)
* improve plot groups and bounds handling * changelog entry * fix potential deadlock * fix two more potential deadlocks * syntax fix * move changelog entry * move category * Update crates/egui/src/widgets/plot/mod.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Update crates/egui_demo_lib/src/demo/plot_demo.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * clean up suggestions * address comments * use the new methods * fix locked bounds * Sync bounds_modified along with the bounds themselves * move changelog entry * Remove set_bounds_auto - not necessary any more * add a comment about bounds modifications --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> Co-authored-by: Jackson Kruger <jackson@farprobe.com>
This commit is contained in:
parent
8a2cfbd131
commit
69b568aeb4
|
|
@ -6,6 +6,8 @@ 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)).
|
||||||
|
|
||||||
|
|
||||||
## 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)):
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
//! Simple plotting library.
|
//! Simple plotting library.
|
||||||
|
|
||||||
use std::{
|
use ahash::HashMap;
|
||||||
cell::{Cell, RefCell},
|
use std::ops::RangeInclusive;
|
||||||
ops::RangeInclusive,
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use epaint::util::FloatOrd;
|
use epaint::util::FloatOrd;
|
||||||
|
|
@ -35,9 +32,11 @@ type AxisFormatter = Option<Box<AxisFormatterFn>>;
|
||||||
type GridSpacerFn = dyn Fn(GridInput) -> Vec<GridMark>;
|
type GridSpacerFn = dyn Fn(GridInput) -> Vec<GridMark>;
|
||||||
type GridSpacer = Box<GridSpacerFn>;
|
type GridSpacer = Box<GridSpacerFn>;
|
||||||
|
|
||||||
|
type CoordinatesFormatterFn = dyn Fn(&PlotPoint, &PlotBounds) -> String;
|
||||||
|
|
||||||
/// Specifies the coordinates formatting when passed to [`Plot::coordinates_formatter`].
|
/// Specifies the coordinates formatting when passed to [`Plot::coordinates_formatter`].
|
||||||
pub struct CoordinatesFormatter {
|
pub struct CoordinatesFormatter {
|
||||||
function: Box<dyn Fn(&PlotPoint, &PlotBounds) -> String>,
|
function: Box<CoordinatesFormatterFn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinatesFormatter {
|
impl CoordinatesFormatter {
|
||||||
|
|
@ -126,120 +125,23 @@ enum Cursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains the cursors drawn for a plot widget in a single frame.
|
/// Contains the cursors drawn for a plot widget in a single frame.
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Clone)]
|
||||||
struct PlotFrameCursors {
|
struct PlotFrameCursors {
|
||||||
id: Id,
|
id: Id,
|
||||||
cursors: Vec<Cursor>,
|
cursors: Vec<Cursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines how multiple plots share the same cursor for one or both of their axes. Can be added while building
|
#[derive(Default, Clone)]
|
||||||
/// a plot with [`Plot::link_cursor`]. Contains an internal state, meaning that this object should be stored by
|
struct CursorLinkGroups(HashMap<Id, Vec<PlotFrameCursors>>);
|
||||||
/// the user between frames.
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone)]
|
||||||
pub struct LinkedCursorsGroup {
|
struct LinkedBounds {
|
||||||
link_x: bool,
|
bounds: PlotBounds,
|
||||||
link_y: bool,
|
bounds_modified: AxisBools,
|
||||||
// We store the cursors drawn for each linked plot. Each time a plot in the group is drawn, the
|
|
||||||
// cursors due to hovering it drew are appended to `frames`, so lower indices are older.
|
|
||||||
// When a plot is redrawn all entries older than its previous entry are removed. This avoids
|
|
||||||
// unbounded growth and also ensures entries for plots which are not longer part of the group
|
|
||||||
// gets removed.
|
|
||||||
frames: Rc<RefCell<Vec<PlotFrameCursors>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinkedCursorsGroup {
|
#[derive(Default, Clone)]
|
||||||
pub fn new(link_x: bool, link_y: bool) -> Self {
|
struct BoundsLinkGroups(HashMap<Id, LinkedBounds>);
|
||||||
Self {
|
|
||||||
link_x,
|
|
||||||
link_y,
|
|
||||||
frames: Rc::new(RefCell::new(Vec::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Only link the cursor for the x-axis.
|
|
||||||
pub fn x() -> Self {
|
|
||||||
Self::new(true, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Only link the cursor for the y-axis.
|
|
||||||
pub fn y() -> Self {
|
|
||||||
Self::new(false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Link the cursors for both axes.
|
|
||||||
pub fn both() -> Self {
|
|
||||||
Self::new(true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change whether the cursor for the x-axis is linked for this group. Using this after plots in this group have been
|
|
||||||
/// drawn in this frame already may lead to unexpected results.
|
|
||||||
pub fn set_link_x(&mut self, link: bool) {
|
|
||||||
self.link_x = link;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change whether the cursor for the y-axis is linked for this group. Using this after plots in this group have been
|
|
||||||
/// drawn in this frame already may lead to unexpected results.
|
|
||||||
pub fn set_link_y(&mut self, link: bool) {
|
|
||||||
self.link_y = link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Defines how multiple plots share the same range for one or both of their axes. Can be added while building
|
|
||||||
/// a plot with [`Plot::link_axis`]. Contains an internal state, meaning that this object should be stored by
|
|
||||||
/// the user between frames.
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub struct LinkedAxisGroup {
|
|
||||||
pub(crate) link_x: bool,
|
|
||||||
pub(crate) link_y: bool,
|
|
||||||
pub(crate) bounds: Rc<Cell<Option<PlotBounds>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LinkedAxisGroup {
|
|
||||||
pub fn new(link_x: bool, link_y: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
link_x,
|
|
||||||
link_y,
|
|
||||||
bounds: Rc::new(Cell::new(None)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Only link the x-axis.
|
|
||||||
pub fn x() -> Self {
|
|
||||||
Self::new(true, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Only link the y-axis.
|
|
||||||
pub fn y() -> Self {
|
|
||||||
Self::new(false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Link both axes. Note that this still respects the aspect ratio of the individual plots.
|
|
||||||
pub fn both() -> Self {
|
|
||||||
Self::new(true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change whether the x-axis is linked for this group. Using this after plots in this group have been
|
|
||||||
/// drawn in this frame already may lead to unexpected results.
|
|
||||||
pub fn set_link_x(&mut self, link: bool) {
|
|
||||||
self.link_x = link;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change whether the y-axis is linked for this group. Using this after plots in this group have been
|
|
||||||
/// drawn in this frame already may lead to unexpected results.
|
|
||||||
pub fn set_link_y(&mut self, link: bool) {
|
|
||||||
self.link_y = link;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&self) -> Option<PlotBounds> {
|
|
||||||
self.bounds.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(&self, bounds: PlotBounds) {
|
|
||||||
self.bounds.set(Some(bounds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -272,8 +174,8 @@ pub struct Plot {
|
||||||
min_auto_bounds: PlotBounds,
|
min_auto_bounds: PlotBounds,
|
||||||
margin_fraction: Vec2,
|
margin_fraction: Vec2,
|
||||||
boxed_zoom_pointer_button: PointerButton,
|
boxed_zoom_pointer_button: PointerButton,
|
||||||
linked_axes: Option<LinkedAxisGroup>,
|
linked_axes: Option<(Id, AxisBools)>,
|
||||||
linked_cursors: Option<LinkedCursorsGroup>,
|
linked_cursors: Option<(Id, AxisBools)>,
|
||||||
|
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
|
|
@ -617,17 +519,29 @@ impl Plot {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a [`LinkedAxisGroup`] so that this plot will share the bounds with other plots that have this
|
/// Add this plot to an axis link group so that this plot will share the bounds with other plots in the
|
||||||
/// group assigned. A plot cannot belong to more than one group.
|
/// same group. A plot cannot belong to more than one axis group.
|
||||||
pub fn link_axis(mut self, group: LinkedAxisGroup) -> Self {
|
pub fn link_axis(mut self, group_id: impl Into<Id>, link_x: bool, link_y: bool) -> Self {
|
||||||
self.linked_axes = Some(group);
|
self.linked_axes = Some((
|
||||||
|
group_id.into(),
|
||||||
|
AxisBools {
|
||||||
|
x: link_x,
|
||||||
|
y: link_y,
|
||||||
|
},
|
||||||
|
));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a [`LinkedCursorsGroup`] so that this plot will share the bounds with other plots that have this
|
/// Add this plot to a cursor link group so that this plot will share the cursor position with other plots
|
||||||
/// group assigned. A plot cannot belong to more than one group.
|
/// in the same group. A plot cannot belong to more than one cursor group.
|
||||||
pub fn link_cursor(mut self, group: LinkedCursorsGroup) -> Self {
|
pub fn link_cursor(mut self, group_id: impl Into<Id>, link_x: bool, link_y: bool) -> Self {
|
||||||
self.linked_cursors = Some(group);
|
self.linked_cursors = Some((
|
||||||
|
group_id.into(),
|
||||||
|
AxisBools {
|
||||||
|
x: link_x,
|
||||||
|
y: link_y,
|
||||||
|
},
|
||||||
|
));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,10 +634,13 @@ impl Plot {
|
||||||
let plot_id = ui.make_persistent_id(id_source);
|
let plot_id = ui.make_persistent_id(id_source);
|
||||||
ui.ctx().check_for_id_clash(plot_id, rect, "Plot");
|
ui.ctx().check_for_id_clash(plot_id, rect, "Plot");
|
||||||
let memory = if reset {
|
let memory = if reset {
|
||||||
if let Some(axes) = linked_axes.as_ref() {
|
if let Some((name, _)) = linked_axes.as_ref() {
|
||||||
axes.bounds.set(None);
|
ui.memory_mut(|memory| {
|
||||||
|
let link_groups: &mut BoundsLinkGroups =
|
||||||
|
memory.data.get_temp_mut_or_default(Id::null());
|
||||||
|
link_groups.0.remove(name);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
PlotMemory::load(ui.ctx(), plot_id)
|
PlotMemory::load(ui.ctx(), plot_id)
|
||||||
|
|
@ -742,7 +659,7 @@ impl Plot {
|
||||||
});
|
});
|
||||||
|
|
||||||
let PlotMemory {
|
let PlotMemory {
|
||||||
bounds_modified,
|
mut bounds_modified,
|
||||||
mut hovered_entry,
|
mut hovered_entry,
|
||||||
mut hidden_items,
|
mut hidden_items,
|
||||||
last_screen_transform,
|
last_screen_transform,
|
||||||
|
|
@ -754,8 +671,8 @@ impl Plot {
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
next_auto_color_idx: 0,
|
next_auto_color_idx: 0,
|
||||||
last_screen_transform,
|
last_screen_transform,
|
||||||
bounds_modified,
|
|
||||||
response,
|
response,
|
||||||
|
bounds_modifications: Vec::new(),
|
||||||
ctx: ui.ctx().clone(),
|
ctx: ui.ctx().clone(),
|
||||||
};
|
};
|
||||||
let inner = build_fn(&mut plot_ui);
|
let inner = build_fn(&mut plot_ui);
|
||||||
|
|
@ -763,7 +680,7 @@ impl Plot {
|
||||||
mut items,
|
mut items,
|
||||||
mut response,
|
mut response,
|
||||||
last_screen_transform,
|
last_screen_transform,
|
||||||
mut bounds_modified,
|
bounds_modifications,
|
||||||
..
|
..
|
||||||
} = plot_ui;
|
} = plot_ui;
|
||||||
|
|
||||||
|
|
@ -801,52 +718,71 @@ impl Plot {
|
||||||
let mut bounds = *last_screen_transform.bounds();
|
let mut bounds = *last_screen_transform.bounds();
|
||||||
|
|
||||||
// Find the cursors from other plots we need to draw
|
// Find the cursors from other plots we need to draw
|
||||||
let draw_cursors: Vec<Cursor> = if let Some(group) = linked_cursors.as_ref() {
|
let draw_cursors: Vec<Cursor> = if let Some((id, _)) = linked_cursors.as_ref() {
|
||||||
let mut frames = group.frames.borrow_mut();
|
ui.memory_mut(|memory| {
|
||||||
|
let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::null());
|
||||||
|
let cursors = frames.0.entry(*id).or_default();
|
||||||
|
|
||||||
// Look for our previous frame
|
// Look for our previous frame
|
||||||
let index = frames
|
let index = cursors
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_, frame)| frame.id == plot_id)
|
.find(|(_, frame)| frame.id == plot_id)
|
||||||
.map(|(i, _)| i);
|
.map(|(i, _)| i);
|
||||||
|
|
||||||
// Remove our previous frame and all older frames as these are no longer displayed. This avoids
|
// Remove our previous frame and all older frames as these are no longer displayed. This avoids
|
||||||
// unbounded growth, as we add an entry each time we draw a plot.
|
// unbounded growth, as we add an entry each time we draw a plot.
|
||||||
index.map(|index| frames.drain(0..=index));
|
index.map(|index| cursors.drain(0..=index));
|
||||||
|
|
||||||
// Gather all cursors of the remaining frames. This will be all the cursors of the
|
// Gather all cursors of the remaining frames. This will be all the cursors of the
|
||||||
// other plots in the group. We want to draw these in the current plot too.
|
// other plots in the group. We want to draw these in the current plot too.
|
||||||
frames
|
cursors
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|frame| frame.cursors.iter().copied())
|
.flat_map(|frame| frame.cursors.iter().copied())
|
||||||
.collect()
|
.collect()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Transfer the bounds from a link group.
|
// Transfer the bounds from a link group.
|
||||||
if let Some(axes) = linked_axes.as_ref() {
|
if let Some((id, axes)) = linked_axes.as_ref() {
|
||||||
if let Some(linked_bounds) = axes.get() {
|
ui.memory_mut(|memory| {
|
||||||
if axes.link_x {
|
let link_groups: &mut BoundsLinkGroups =
|
||||||
bounds.set_x(&linked_bounds);
|
memory.data.get_temp_mut_or_default(Id::null());
|
||||||
// Mark the axis as modified to prevent it from being changed.
|
if let Some(linked_bounds) = link_groups.0.get(id) {
|
||||||
bounds_modified.x = true;
|
if axes.x {
|
||||||
}
|
bounds.set_x(&linked_bounds.bounds);
|
||||||
if axes.link_y {
|
bounds_modified.x = linked_bounds.bounds_modified.x;
|
||||||
bounds.set_y(&linked_bounds);
|
}
|
||||||
// Mark the axis as modified to prevent it from being changed.
|
if axes.y {
|
||||||
bounds_modified.y = true;
|
bounds.set_y(&linked_bounds.bounds);
|
||||||
}
|
bounds_modified.y = linked_bounds.bounds_modified.y;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allow double clicking to reset to the initial bounds?
|
// Allow double clicking to reset to the initial bounds.
|
||||||
if allow_double_click_reset && response.double_clicked_by(PointerButton::Primary) {
|
if allow_double_click_reset && response.double_clicked() {
|
||||||
bounds_modified = false.into();
|
bounds_modified = false.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset bounds to initial bounds if we haven't been modified.
|
// Apply bounds modifications.
|
||||||
|
for modification in bounds_modifications {
|
||||||
|
match modification {
|
||||||
|
BoundsModification::Set(new_bounds) => {
|
||||||
|
bounds = new_bounds;
|
||||||
|
bounds_modified = true.into();
|
||||||
|
}
|
||||||
|
BoundsModification::Translate(delta) => {
|
||||||
|
bounds.translate(delta);
|
||||||
|
bounds_modified = true.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset bounds to initial bounds if they haven't been modified.
|
||||||
if !bounds_modified.x {
|
if !bounds_modified.x {
|
||||||
bounds.set_x(&min_auto_bounds);
|
bounds.set_x(&min_auto_bounds);
|
||||||
}
|
}
|
||||||
|
|
@ -861,7 +797,6 @@ impl Plot {
|
||||||
if auto_x || auto_y {
|
if auto_x || auto_y {
|
||||||
for item in &items {
|
for item in &items {
|
||||||
let item_bounds = item.bounds();
|
let item_bounds = item.bounds();
|
||||||
|
|
||||||
if auto_x {
|
if auto_x {
|
||||||
bounds.merge_x(&item_bounds);
|
bounds.merge_x(&item_bounds);
|
||||||
}
|
}
|
||||||
|
|
@ -883,8 +818,8 @@ impl Plot {
|
||||||
|
|
||||||
// Enforce aspect ratio
|
// Enforce aspect ratio
|
||||||
if let Some(data_aspect) = data_aspect {
|
if let Some(data_aspect) = data_aspect {
|
||||||
if let Some(linked_axes) = &linked_axes {
|
if let Some((_, linked_axes)) = &linked_axes {
|
||||||
let change_x = linked_axes.link_y && !linked_axes.link_x;
|
let change_x = linked_axes.y && !linked_axes.x;
|
||||||
transform.set_aspect_by_changing_axis(data_aspect as f64, change_x);
|
transform.set_aspect_by_changing_axis(data_aspect as f64, change_x);
|
||||||
} else if auto_bounds.any() {
|
} else if auto_bounds.any() {
|
||||||
transform.set_aspect_by_expanding(data_aspect as f64);
|
transform.set_aspect_by_expanding(data_aspect as f64);
|
||||||
|
|
@ -952,7 +887,8 @@ impl Plot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hover_pos) = response.hover_pos() {
|
let hover_pos = response.hover_pos();
|
||||||
|
if let Some(hover_pos) = hover_pos {
|
||||||
if allow_zoom {
|
if allow_zoom {
|
||||||
let zoom_factor = if data_aspect.is_some() {
|
let zoom_factor = if data_aspect.is_some() {
|
||||||
Vec2::splat(ui.input(|i| i.zoom_delta()))
|
Vec2::splat(ui.input(|i| i.zoom_delta()))
|
||||||
|
|
@ -987,8 +923,8 @@ impl Plot {
|
||||||
axis_formatters,
|
axis_formatters,
|
||||||
show_axes,
|
show_axes,
|
||||||
transform: transform.clone(),
|
transform: transform.clone(),
|
||||||
draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.link_x),
|
draw_cursor_x: linked_cursors.as_ref().map_or(false, |(_, group)| group.x),
|
||||||
draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.link_y),
|
draw_cursor_y: linked_cursors.as_ref().map_or(false, |(_, group)| group.y),
|
||||||
draw_cursors,
|
draw_cursors,
|
||||||
grid_spacers,
|
grid_spacers,
|
||||||
sharp_grid_lines,
|
sharp_grid_lines,
|
||||||
|
|
@ -1007,16 +943,31 @@ impl Plot {
|
||||||
hovered_entry = legend.hovered_entry_name();
|
hovered_entry = legend.hovered_entry_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(group) = linked_cursors.as_ref() {
|
if let Some((id, _)) = linked_cursors.as_ref() {
|
||||||
// Push the frame we just drew to the list of frames
|
// Push the frame we just drew to the list of frames
|
||||||
group.frames.borrow_mut().push(PlotFrameCursors {
|
ui.memory_mut(|memory| {
|
||||||
id: plot_id,
|
let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::null());
|
||||||
cursors: plot_cursors,
|
let cursors = frames.0.entry(*id).or_default();
|
||||||
|
cursors.push(PlotFrameCursors {
|
||||||
|
id: plot_id,
|
||||||
|
cursors: plot_cursors,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(group) = linked_axes.as_ref() {
|
if let Some((id, _)) = linked_axes.as_ref() {
|
||||||
group.set(*transform.bounds());
|
// Save the linked bounds.
|
||||||
|
ui.memory_mut(|memory| {
|
||||||
|
let link_groups: &mut BoundsLinkGroups =
|
||||||
|
memory.data.get_temp_mut_or_default(Id::null());
|
||||||
|
link_groups.0.insert(
|
||||||
|
*id,
|
||||||
|
LinkedBounds {
|
||||||
|
bounds: *transform.bounds(),
|
||||||
|
bounds_modified,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let memory = PlotMemory {
|
let memory = PlotMemory {
|
||||||
|
|
@ -1038,14 +989,21 @@ impl Plot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// User-requested modifications to the plot bounds. We collect them in the plot build function to later apply
|
||||||
|
/// them at the right time, as other modifications need to happen first.
|
||||||
|
enum BoundsModification {
|
||||||
|
Set(PlotBounds),
|
||||||
|
Translate(Vec2),
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides methods to interact with a plot while building it. It is the single argument of the closure
|
/// Provides methods to interact with a plot while building it. It is the single argument of the closure
|
||||||
/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it.
|
/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it.
|
||||||
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: ScreenTransform,
|
last_screen_transform: ScreenTransform,
|
||||||
bounds_modified: AxisBools,
|
|
||||||
response: Response,
|
response: Response,
|
||||||
|
bounds_modifications: Vec<BoundsModification>,
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1071,14 +1029,14 @@ impl PlotUi {
|
||||||
|
|
||||||
/// Set the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
/// Set the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
||||||
pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) {
|
pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) {
|
||||||
self.last_screen_transform.set_bounds(plot_bounds);
|
self.bounds_modifications
|
||||||
self.bounds_modified = true.into();
|
.push(BoundsModification::Set(plot_bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
/// Move the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
||||||
pub fn translate_bounds(&mut self, delta_pos: Vec2) {
|
pub fn translate_bounds(&mut self, delta_pos: Vec2) {
|
||||||
self.last_screen_transform.translate_bounds(delta_pos);
|
self.bounds_modifications
|
||||||
self.bounds_modified = true.into();
|
.push(BoundsModification::Translate(delta_pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the plot area is currently hovered.
|
/// Returns `true` if the plot area is currently hovered.
|
||||||
|
|
@ -1355,7 +1313,8 @@ impl PreparedPlot {
|
||||||
item.shapes(&mut plot_ui, transform, &mut shapes);
|
item.shapes(&mut plot_ui, transform, &mut shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cursors = if let Some(pointer) = response.hover_pos() {
|
let hover_pos = response.hover_pos();
|
||||||
|
let cursors = if let Some(pointer) = hover_pos {
|
||||||
self.hover(ui, pointer, &mut shapes)
|
self.hover(ui, pointer, &mut shapes)
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|
@ -1396,7 +1355,8 @@ impl PreparedPlot {
|
||||||
painter.extend(shapes);
|
painter.extend(shapes);
|
||||||
|
|
||||||
if let Some((corner, formatter)) = self.coordinates_formatter.as_ref() {
|
if let Some((corner, formatter)) = self.coordinates_formatter.as_ref() {
|
||||||
if let Some(pointer) = response.hover_pos() {
|
let hover_pos = response.hover_pos();
|
||||||
|
if let Some(pointer) = hover_pos {
|
||||||
let font_id = TextStyle::Monospace.resolve(ui.style());
|
let font_id = TextStyle::Monospace.resolve(ui.style());
|
||||||
let coordinate = transform.value_from_position(pointer);
|
let coordinate = transform.value_from_position(pointer);
|
||||||
let text = formatter.format(&coordinate, transform.bounds());
|
let text = formatter.format(&coordinate, transform.bounds());
|
||||||
|
|
|
||||||
|
|
@ -576,8 +576,6 @@ impl CustomAxisDemo {
|
||||||
struct LinkedAxisDemo {
|
struct LinkedAxisDemo {
|
||||||
link_x: bool,
|
link_x: bool,
|
||||||
link_y: bool,
|
link_y: bool,
|
||||||
group: plot::LinkedAxisGroup,
|
|
||||||
cursor_group: plot::LinkedCursorsGroup,
|
|
||||||
link_cursor_x: bool,
|
link_cursor_x: bool,
|
||||||
link_cursor_y: bool,
|
link_cursor_y: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -591,8 +589,6 @@ impl Default for LinkedAxisDemo {
|
||||||
Self {
|
Self {
|
||||||
link_x,
|
link_x,
|
||||||
link_y,
|
link_y,
|
||||||
group: plot::LinkedAxisGroup::new(link_x, link_y),
|
|
||||||
cursor_group: plot::LinkedCursorsGroup::new(link_cursor_x, link_cursor_y),
|
|
||||||
link_cursor_x,
|
link_cursor_x,
|
||||||
link_cursor_y,
|
link_cursor_y,
|
||||||
}
|
}
|
||||||
|
|
@ -638,37 +634,35 @@ impl LinkedAxisDemo {
|
||||||
ui.checkbox(&mut self.link_x, "X");
|
ui.checkbox(&mut self.link_x, "X");
|
||||||
ui.checkbox(&mut self.link_y, "Y");
|
ui.checkbox(&mut self.link_y, "Y");
|
||||||
});
|
});
|
||||||
self.group.set_link_x(self.link_x);
|
|
||||||
self.group.set_link_y(self.link_y);
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Linked cursors:");
|
ui.label("Linked cursors:");
|
||||||
ui.checkbox(&mut self.link_cursor_x, "X");
|
ui.checkbox(&mut self.link_cursor_x, "X");
|
||||||
ui.checkbox(&mut self.link_cursor_y, "Y");
|
ui.checkbox(&mut self.link_cursor_y, "Y");
|
||||||
});
|
});
|
||||||
self.cursor_group.set_link_x(self.link_cursor_x);
|
|
||||||
self.cursor_group.set_link_y(self.link_cursor_y);
|
let link_group_id = ui.id().with("linked_demo");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
Plot::new("linked_axis_1")
|
Plot::new("linked_axis_1")
|
||||||
.data_aspect(1.0)
|
.data_aspect(1.0)
|
||||||
.width(250.0)
|
.width(250.0)
|
||||||
.height(250.0)
|
.height(250.0)
|
||||||
.link_axis(self.group.clone())
|
.link_axis(link_group_id, self.link_x, self.link_y)
|
||||||
.link_cursor(self.cursor_group.clone())
|
.link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y)
|
||||||
.show(ui, LinkedAxisDemo::configure_plot);
|
.show(ui, LinkedAxisDemo::configure_plot);
|
||||||
Plot::new("linked_axis_2")
|
Plot::new("linked_axis_2")
|
||||||
.data_aspect(2.0)
|
.data_aspect(2.0)
|
||||||
.width(150.0)
|
.width(150.0)
|
||||||
.height(250.0)
|
.height(250.0)
|
||||||
.link_axis(self.group.clone())
|
.link_axis(link_group_id, self.link_x, self.link_y)
|
||||||
.link_cursor(self.cursor_group.clone())
|
.link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y)
|
||||||
.show(ui, LinkedAxisDemo::configure_plot);
|
.show(ui, LinkedAxisDemo::configure_plot);
|
||||||
});
|
});
|
||||||
Plot::new("linked_axis_3")
|
Plot::new("linked_axis_3")
|
||||||
.data_aspect(0.5)
|
.data_aspect(0.5)
|
||||||
.width(250.0)
|
.width(250.0)
|
||||||
.height(150.0)
|
.height(150.0)
|
||||||
.link_axis(self.group.clone())
|
.link_axis(link_group_id, self.link_x, self.link_y)
|
||||||
.link_cursor(self.cursor_group.clone())
|
.link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y)
|
||||||
.show(ui, LinkedAxisDemo::configure_plot)
|
.show(ui, LinkedAxisDemo::configure_plot)
|
||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue