Skip to content

Commit

Permalink
Make egui_plot::PlotMemory public (#3871)
Browse files Browse the repository at this point in the history
This allows users to e.g. read/write the plot bounds/transform before
and after showing a `Plot`.
  • Loading branch information
emilk authored Jan 23, 2024
1 parent aa67d31 commit 2f9a4ca
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 98 deletions.
15 changes: 14 additions & 1 deletion crates/egui_demo_lib/src/demo/plot_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,20 @@ struct InteractionDemo {}
impl InteractionDemo {
#[allow(clippy::unused_self)]
fn ui(&mut self, ui: &mut Ui) -> Response {
let plot = Plot::new("interaction_demo").height(300.0);
let id = ui.make_persistent_id("interaction_demo");

// This demonstrates how to read info about the plot _before_ showing it:
let plot_memory = egui_plot::PlotMemory::load(ui.ctx(), id);
if let Some(plot_memory) = plot_memory {
let bounds = plot_memory.bounds();
ui.label(format!(
"plot bounds: min: {:.02?}, max: {:.02?}",
bounds.min(),
bounds.max()
));
}

let plot = Plot::new("interaction_demo").id(id).height(300.0);

let PlotResponse {
response,
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_plot/src/legend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ impl LegendWidget {
}

// Get the name of the hovered items.
pub fn hovered_entry_name(&self) -> Option<String> {
pub fn hovered_item_name(&self) -> Option<String> {
self.entries
.iter()
.find(|(_, entry)| entry.hovered)
Expand Down
92 changes: 34 additions & 58 deletions crates/egui_plot/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
//! Simple plotting library for [`egui`](https://github.com/emilk/egui).
//!
//! Check out [`Plot`] for how to get started.
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!

mod axis;
mod items;
mod legend;
mod memory;
mod transform;

use std::{ops::RangeInclusive, sync::Arc};

use egui::ahash::HashMap;
Expand All @@ -26,11 +34,7 @@ pub use transform::{PlotBounds, PlotTransform};
use items::{horizontal_line, rulers_color, vertical_line};

pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement};

mod axis;
mod items;
mod legend;
mod transform;
pub use memory::PlotMemory;

type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String;
type LabelFormatter = Option<Box<LabelFormatterFn>>;
Expand Down Expand Up @@ -77,44 +81,6 @@ impl Default for CoordinatesFormatter {

const MIN_LINE_SPACING_IN_POINTS: f64 = 6.0; // TODO(emilk): large enough for a wide label

/// Information about the plot that has to persist between frames.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone)]
struct PlotMemory {
/// Indicates if the plot uses automatic bounds. This is disengaged whenever the user modifies
/// the bounds, for example by moving or zooming.
auto_bounds: Vec2b,

hovered_entry: Option<String>,
hidden_items: ahash::HashSet<String>,
last_plot_transform: PlotTransform,

/// Allows to remember the first click position when performing a boxed zoom
last_click_pos_for_zoom: Option<Pos2>,
}

#[cfg(feature = "serde")]
impl PlotMemory {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_persisted(id))
}

pub fn store(self, ctx: &Context, id: Id) {
ctx.data_mut(|d| d.insert_persisted(id, self));
}
}

#[cfg(not(feature = "serde"))]
impl PlotMemory {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_temp(id))
}

pub fn store(self, ctx: &Context, id: Id) {
ctx.data_mut(|d| d.insert_temp(id, self));
}
}

// ----------------------------------------------------------------------------

/// Indicates a vertical or horizontal cursor line in plot coordinates.
Expand Down Expand Up @@ -166,6 +132,7 @@ pub struct PlotResponse<R> {
/// ```
/// # egui::__run_test_ui(|ui| {
/// use egui_plot::{Line, Plot, PlotPoints};
///
/// let sin: PlotPoints = (0..1000).map(|i| {
/// let x = i as f64 * 0.01;
/// [x, x.sin()]
Expand All @@ -176,6 +143,7 @@ pub struct PlotResponse<R> {
/// ```
pub struct Plot {
id_source: Id,
id: Option<Id>,

center_axis: Vec2b,
allow_zoom: Vec2b,
Expand Down Expand Up @@ -218,6 +186,7 @@ impl Plot {
pub fn new(id_source: impl std::hash::Hash) -> Self {
Self {
id_source: Id::new(id_source),
id: None,

center_axis: false.into(),
allow_zoom: true.into(),
Expand Down Expand Up @@ -256,6 +225,17 @@ impl Plot {
}
}

/// Set an explicit (global) id for the plot.
///
/// This will override the id set by [`Self::new`].
///
/// This is the same `Id` that can be used for [`PlotMemory::load`].
#[inline]
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
}

/// width / height ratio of the data.
/// For instance, it can be useful to set this to `1.0` for when the two axes show the same
/// unit.
Expand Down Expand Up @@ -716,6 +696,7 @@ impl Plot {
) -> PlotResponse<R> {
let Self {
id_source,
id,
center_axis,
allow_zoom,
allow_drag,
Expand Down Expand Up @@ -850,7 +831,7 @@ impl Plot {
let rect = plot_rect;

// Load or initialize the memory.
let plot_id = ui.make_persistent_id(id_source);
let plot_id = id.unwrap_or_else(|| ui.make_persistent_id(id_source));
ui.ctx().check_for_id_clash(plot_id, rect, "Plot");
let memory = if reset {
if let Some((name, _)) = linked_axes.as_ref() {
Expand All @@ -865,22 +846,17 @@ impl Plot {
}
.unwrap_or_else(|| PlotMemory {
auto_bounds: default_auto_bounds,
hovered_entry: None,
hovered_item: None,
hidden_items: Default::default(),
last_plot_transform: PlotTransform::new(
rect,
min_auto_bounds,
center_axis.x,
center_axis.y,
),
transform: PlotTransform::new(rect, min_auto_bounds, center_axis.x, center_axis.y),
last_click_pos_for_zoom: None,
});

let PlotMemory {
mut auto_bounds,
mut hovered_entry,
mut hovered_item,
mut hidden_items,
last_plot_transform,
transform: last_plot_transform,
mut last_click_pos_for_zoom,
} = memory;

Expand Down Expand Up @@ -919,14 +895,14 @@ impl Plot {
let legend = legend_config
.and_then(|config| LegendWidget::try_new(rect, config, &items, &hidden_items));
// Don't show hover cursor when hovering over legend.
if hovered_entry.is_some() {
if hovered_item.is_some() {
show_x = false;
show_y = false;
}
// Remove the deselected items.
items.retain(|item| !hidden_items.contains(item.name()));
// Highlight the hovered items.
if let Some(hovered_name) = &hovered_entry {
if let Some(hovered_name) = &hovered_item {
items
.iter_mut()
.filter(|entry| entry.name() == hovered_name)
Expand Down Expand Up @@ -1211,7 +1187,7 @@ impl Plot {
if let Some(mut legend) = legend {
ui.add(&mut legend);
hidden_items = legend.hidden_items();
hovered_entry = legend.hovered_entry_name();
hovered_item = legend.hovered_item_name();
}

if let Some((id, _)) = linked_cursors.as_ref() {
Expand Down Expand Up @@ -1242,9 +1218,9 @@ impl Plot {

let memory = PlotMemory {
auto_bounds,
hovered_entry,
hovered_item,
hidden_items,
last_plot_transform: transform,
transform,
last_click_pos_for_zoom,
};
memory.store(ui.ctx(), plot_id);
Expand Down
67 changes: 53 additions & 14 deletions crates/egui_plot/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
use epaint::Pos2;
use egui::{ahash, Context, Id, Pos2, Vec2b};

use crate::{Context, Id};

use super::{transform::ScreenTransform, AxisBools};
use crate::{PlotBounds, PlotTransform};

/// Information about the plot that has to persist between frames.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone)]
pub(super) struct PlotMemory {
/// Indicates if the user has modified the bounds, for example by moving or zooming,
/// or if the bounds should be calculated based by included point or auto bounds.
pub(super) bounds_modified: AxisBools,
pub struct PlotMemory {
/// Indicates if the plot uses automatic bounds.
///
/// This is set to `false` whenever the user modifies
/// the bounds, for example by moving or zooming.
pub auto_bounds: Vec2b,

pub(super) hovered_entry: Option<String>,
/// Which item is hovered?
pub hovered_item: Option<String>,

pub(super) hidden_items: ahash::HashSet<String>,
/// Which items _not_ to show?
pub hidden_items: ahash::HashSet<String>,

pub(super) last_screen_transform: ScreenTransform,
/// The transform from last frame.
pub(crate) transform: PlotTransform,

/// Allows to remember the first click position when performing a boxed zoom
pub(super) last_click_pos_for_zoom: Option<Pos2>,
pub(crate) last_click_pos_for_zoom: Option<Pos2>,
}

impl PlotMemory {
#[inline]
pub fn transform(&self) -> PlotTransform {
self.transform
}

#[inline]
pub fn set_transform(&mut self, t: PlotTransform) {
self.transform = t;
}

/// Plot-space bounds.
#[inline]
pub fn bounds(&self) -> &PlotBounds {
self.transform.bounds()
}

/// Plot-space bounds.
#[inline]
pub fn set_bounds(&mut self, bounds: PlotBounds) {
self.transform.set_bounds(bounds);
}
}

#[cfg(feature = "serde")]
impl PlotMemory {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_persisted(id))
}

pub fn store(self, ctx: &Context, id: Id) {
ctx.data_mut(|d| d.insert_persisted(id, self));
}
}

#[cfg(not(feature = "serde"))]
impl PlotMemory {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data().get_persisted(id)
ctx.data_mut(|d| d.get_temp(id))
}

pub fn store(self, ctx: &Context, id: Id) {
ctx.data().insert_persisted(id, self);
ctx.data_mut(|d| d.insert_temp(id, self));
}
}
Loading

0 comments on commit 2f9a4ca

Please sign in to comment.