Make `egui_plot::PlotMemory` public (#3871)

This allows users to e.g. read/write the plot bounds/transform before
and after showing a `Plot`.
This commit is contained in:
Emil Ernerfeldt 2024-01-23 09:47:47 +01:00 committed by GitHub
parent aa67d3180b
commit 2f9a4ca6e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 163 additions and 98 deletions

View File

@ -769,7 +769,20 @@ struct InteractionDemo {}
impl InteractionDemo { impl InteractionDemo {
#[allow(clippy::unused_self)] #[allow(clippy::unused_self)]
fn ui(&mut self, ui: &mut Ui) -> Response { 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 { let PlotResponse {
response, response,

View File

@ -226,7 +226,7 @@ impl LegendWidget {
} }
// Get the name of the hovered items. // 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 self.entries
.iter() .iter()
.find(|(_, entry)| entry.hovered) .find(|(_, entry)| entry.hovered)

View File

@ -1,9 +1,17 @@
//! Simple plotting library for [`egui`](https://github.com/emilk/egui). //! Simple plotting library for [`egui`](https://github.com/emilk/egui).
//! //!
//! Check out [`Plot`] for how to get started.
//!
//! ## Feature flags //! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] #![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 std::{ops::RangeInclusive, sync::Arc};
use egui::ahash::HashMap; use egui::ahash::HashMap;
@ -26,11 +34,7 @@ pub use transform::{PlotBounds, PlotTransform};
use items::{horizontal_line, rulers_color, vertical_line}; use items::{horizontal_line, rulers_color, vertical_line};
pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}; pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement};
pub use memory::PlotMemory;
mod axis;
mod items;
mod legend;
mod transform;
type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String; type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String;
type LabelFormatter = Option<Box<LabelFormatterFn>>; type LabelFormatter = Option<Box<LabelFormatterFn>>;
@ -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 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. /// Indicates a vertical or horizontal cursor line in plot coordinates.
@ -166,6 +132,7 @@ pub struct PlotResponse<R> {
/// ``` /// ```
/// # egui::__run_test_ui(|ui| { /// # egui::__run_test_ui(|ui| {
/// use egui_plot::{Line, Plot, PlotPoints}; /// use egui_plot::{Line, Plot, PlotPoints};
///
/// let sin: PlotPoints = (0..1000).map(|i| { /// let sin: PlotPoints = (0..1000).map(|i| {
/// let x = i as f64 * 0.01; /// let x = i as f64 * 0.01;
/// [x, x.sin()] /// [x, x.sin()]
@ -176,6 +143,7 @@ pub struct PlotResponse<R> {
/// ``` /// ```
pub struct Plot { pub struct Plot {
id_source: Id, id_source: Id,
id: Option<Id>,
center_axis: Vec2b, center_axis: Vec2b,
allow_zoom: Vec2b, allow_zoom: Vec2b,
@ -218,6 +186,7 @@ impl Plot {
pub fn new(id_source: impl std::hash::Hash) -> Self { pub fn new(id_source: impl std::hash::Hash) -> Self {
Self { Self {
id_source: Id::new(id_source), id_source: Id::new(id_source),
id: None,
center_axis: false.into(), center_axis: false.into(),
allow_zoom: true.into(), allow_zoom: true.into(),
@ -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. /// 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 /// For instance, it can be useful to set this to `1.0` for when the two axes show the same
/// unit. /// unit.
@ -716,6 +696,7 @@ impl Plot {
) -> PlotResponse<R> { ) -> PlotResponse<R> {
let Self { let Self {
id_source, id_source,
id,
center_axis, center_axis,
allow_zoom, allow_zoom,
allow_drag, allow_drag,
@ -850,7 +831,7 @@ impl Plot {
let rect = plot_rect; let rect = plot_rect;
// Load or initialize the memory. // 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"); ui.ctx().check_for_id_clash(plot_id, rect, "Plot");
let memory = if reset { let memory = if reset {
if let Some((name, _)) = linked_axes.as_ref() { if let Some((name, _)) = linked_axes.as_ref() {
@ -865,22 +846,17 @@ impl Plot {
} }
.unwrap_or_else(|| PlotMemory { .unwrap_or_else(|| PlotMemory {
auto_bounds: default_auto_bounds, auto_bounds: default_auto_bounds,
hovered_entry: None, hovered_item: None,
hidden_items: Default::default(), hidden_items: Default::default(),
last_plot_transform: PlotTransform::new( transform: PlotTransform::new(rect, min_auto_bounds, center_axis.x, center_axis.y),
rect,
min_auto_bounds,
center_axis.x,
center_axis.y,
),
last_click_pos_for_zoom: None, last_click_pos_for_zoom: None,
}); });
let PlotMemory { let PlotMemory {
mut auto_bounds, mut auto_bounds,
mut hovered_entry, mut hovered_item,
mut hidden_items, mut hidden_items,
last_plot_transform, transform: last_plot_transform,
mut last_click_pos_for_zoom, mut last_click_pos_for_zoom,
} = memory; } = memory;
@ -919,14 +895,14 @@ impl Plot {
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.
if hovered_entry.is_some() { if hovered_item.is_some() {
show_x = false; show_x = false;
show_y = false; show_y = false;
} }
// Remove the deselected items. // Remove the deselected items.
items.retain(|item| !hidden_items.contains(item.name())); items.retain(|item| !hidden_items.contains(item.name()));
// Highlight the hovered items. // Highlight the hovered items.
if let Some(hovered_name) = &hovered_entry { if let Some(hovered_name) = &hovered_item {
items items
.iter_mut() .iter_mut()
.filter(|entry| entry.name() == hovered_name) .filter(|entry| entry.name() == hovered_name)
@ -1211,7 +1187,7 @@ impl Plot {
if let Some(mut legend) = legend { if let Some(mut legend) = legend {
ui.add(&mut legend); ui.add(&mut legend);
hidden_items = legend.hidden_items(); 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() { if let Some((id, _)) = linked_cursors.as_ref() {
@ -1242,9 +1218,9 @@ impl Plot {
let memory = PlotMemory { let memory = PlotMemory {
auto_bounds, auto_bounds,
hovered_entry, hovered_item,
hidden_items, hidden_items,
last_plot_transform: transform, transform,
last_click_pos_for_zoom, last_click_pos_for_zoom,
}; };
memory.store(ui.ctx(), plot_id); memory.store(ui.ctx(), plot_id);

View File

@ -1,33 +1,72 @@
use epaint::Pos2; use egui::{ahash, Context, Id, Pos2, Vec2b};
use crate::{Context, Id}; use crate::{PlotBounds, PlotTransform};
use super::{transform::ScreenTransform, AxisBools};
/// Information about the plot that has to persist between frames. /// Information about the plot that has to persist between frames.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone)] #[derive(Clone)]
pub(super) struct PlotMemory { pub struct PlotMemory {
/// Indicates if the user has modified the bounds, for example by moving or zooming, /// Indicates if the plot uses automatic bounds.
/// or if the bounds should be calculated based by included point or auto bounds. ///
pub(super) bounds_modified: AxisBools, /// 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 /// 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 { impl PlotMemory {
pub fn load(ctx: &Context, id: Id) -> Option<Self> { pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data().get_persisted(id) ctx.data_mut(|d| d.get_persisted(id))
} }
pub fn store(self, ctx: &Context, id: Id) { pub fn store(self, ctx: &Context, id: Id) {
ctx.data().insert_persisted(id, self); 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));
} }
} }

View File

@ -4,6 +4,7 @@ use super::PlotPoint;
use crate::*; use crate::*;
/// 2D bounding box of f64 precision. /// 2D bounding box of f64 precision.
///
/// 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))]
@ -18,25 +19,30 @@ impl PlotBounds {
max: [-f64::INFINITY; 2], max: [-f64::INFINITY; 2],
}; };
#[inline]
pub fn from_min_max(min: [f64; 2], max: [f64; 2]) -> Self { pub fn from_min_max(min: [f64; 2], max: [f64; 2]) -> Self {
Self { min, max } Self { min, max }
} }
#[inline]
pub fn min(&self) -> [f64; 2] { pub fn min(&self) -> [f64; 2] {
self.min self.min
} }
#[inline]
pub fn max(&self) -> [f64; 2] { pub fn max(&self) -> [f64; 2] {
self.max self.max
} }
pub(crate) fn new_symmetrical(half_extent: f64) -> Self { #[inline]
pub 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],
} }
} }
#[inline]
pub fn is_finite(&self) -> bool { pub fn is_finite(&self) -> bool {
self.min[0].is_finite() self.min[0].is_finite()
&& self.min[1].is_finite() && self.min[1].is_finite()
@ -44,34 +50,42 @@ impl PlotBounds {
&& self.max[1].is_finite() && self.max[1].is_finite()
} }
#[inline]
pub fn is_finite_x(&self) -> bool { pub fn is_finite_x(&self) -> bool {
self.min[0].is_finite() && self.max[0].is_finite() self.min[0].is_finite() && self.max[0].is_finite()
} }
#[inline]
pub fn is_finite_y(&self) -> bool { pub fn is_finite_y(&self) -> bool {
self.min[1].is_finite() && self.max[1].is_finite() self.min[1].is_finite() && self.max[1].is_finite()
} }
#[inline]
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
self.is_finite() && self.width() > 0.0 && self.height() > 0.0 self.is_finite() && self.width() > 0.0 && self.height() > 0.0
} }
#[inline]
pub fn is_valid_x(&self) -> bool { pub fn is_valid_x(&self) -> bool {
self.is_finite_x() && self.width() > 0.0 self.is_finite_x() && self.width() > 0.0
} }
#[inline]
pub fn is_valid_y(&self) -> bool { pub fn is_valid_y(&self) -> bool {
self.is_finite_y() && self.height() > 0.0 self.is_finite_y() && self.height() > 0.0
} }
#[inline]
pub fn width(&self) -> f64 { pub fn width(&self) -> f64 {
self.max[0] - self.min[0] self.max[0] - self.min[0]
} }
#[inline]
pub fn height(&self) -> f64 { pub fn height(&self) -> f64 {
self.max[1] - self.min[1] self.max[1] - self.min[1]
} }
#[inline]
pub fn center(&self) -> PlotPoint { pub fn center(&self) -> PlotPoint {
[ [
(self.min[0] + self.max[0]) / 2.0, (self.min[0] + self.max[0]) / 2.0,
@ -81,107 +95,127 @@ impl PlotBounds {
} }
/// Expand to include the given (x,y) value /// Expand to include the given (x,y) value
pub(crate) fn extend_with(&mut self, value: &PlotPoint) { #[inline]
pub fn extend_with(&mut self, value: &PlotPoint) {
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(crate) fn extend_with_x(&mut self, x: f64) { #[inline]
pub 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(crate) fn extend_with_y(&mut self, y: f64) { #[inline]
pub 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(crate) fn expand_x(&mut self, pad: f64) { #[inline]
pub fn expand_x(&mut self, pad: f64) {
self.min[0] -= pad; self.min[0] -= pad;
self.max[0] += pad; self.max[0] += pad;
} }
pub(crate) fn expand_y(&mut self, pad: f64) { #[inline]
pub fn expand_y(&mut self, pad: f64) {
self.min[1] -= pad; self.min[1] -= pad;
self.max[1] += pad; self.max[1] += pad;
} }
pub(crate) fn merge_x(&mut self, other: &Self) { #[inline]
pub fn merge_x(&mut self, other: &Self) {
self.min[0] = self.min[0].min(other.min[0]); self.min[0] = self.min[0].min(other.min[0]);
self.max[0] = self.max[0].max(other.max[0]); self.max[0] = self.max[0].max(other.max[0]);
} }
pub(crate) fn merge_y(&mut self, other: &Self) { #[inline]
pub fn merge_y(&mut self, other: &Self) {
self.min[1] = self.min[1].min(other.min[1]); self.min[1] = self.min[1].min(other.min[1]);
self.max[1] = self.max[1].max(other.max[1]); self.max[1] = self.max[1].max(other.max[1]);
} }
pub(crate) fn set_x(&mut self, other: &Self) { #[inline]
pub fn set_x(&mut self, other: &Self) {
self.min[0] = other.min[0]; self.min[0] = other.min[0];
self.max[0] = other.max[0]; self.max[0] = other.max[0];
} }
pub(crate) fn set_y(&mut self, other: &Self) { #[inline]
pub fn set_y(&mut self, other: &Self) {
self.min[1] = other.min[1]; self.min[1] = other.min[1];
self.max[1] = other.max[1]; self.max[1] = other.max[1];
} }
pub(crate) fn merge(&mut self, other: &Self) { #[inline]
pub fn merge(&mut self, other: &Self) {
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(crate) fn translate_x(&mut self, delta: f64) { #[inline]
pub fn translate_x(&mut self, delta: f64) {
self.min[0] += delta; self.min[0] += delta;
self.max[0] += delta; self.max[0] += delta;
} }
pub(crate) fn translate_y(&mut self, delta: f64) { #[inline]
pub fn translate_y(&mut self, delta: f64) {
self.min[1] += delta; self.min[1] += delta;
self.max[1] += delta; self.max[1] += delta;
} }
pub(crate) fn translate(&mut self, delta: Vec2) { #[inline]
pub 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(crate) fn zoom(&mut self, zoom_factor: Vec2, center: PlotPoint) { #[inline]
pub fn zoom(&mut self, zoom_factor: Vec2, center: PlotPoint) {
self.min[0] = center.x + (self.min[0] - center.x) / (zoom_factor.x as f64); self.min[0] = center.x + (self.min[0] - center.x) / (zoom_factor.x as f64);
self.max[0] = center.x + (self.max[0] - center.x) / (zoom_factor.x as f64); self.max[0] = center.x + (self.max[0] - center.x) / (zoom_factor.x as f64);
self.min[1] = center.y + (self.min[1] - center.y) / (zoom_factor.y as f64); self.min[1] = center.y + (self.min[1] - center.y) / (zoom_factor.y as f64);
self.max[1] = center.y + (self.max[1] - center.y) / (zoom_factor.y as f64); self.max[1] = center.y + (self.max[1] - center.y) / (zoom_factor.y as f64);
} }
pub(crate) fn add_relative_margin_x(&mut self, margin_fraction: Vec2) { #[inline]
pub fn add_relative_margin_x(&mut self, margin_fraction: Vec2) {
let width = self.width().max(0.0); let width = self.width().max(0.0);
self.expand_x(margin_fraction.x as f64 * width); self.expand_x(margin_fraction.x as f64 * width);
} }
pub(crate) fn add_relative_margin_y(&mut self, margin_fraction: Vec2) { #[inline]
pub fn add_relative_margin_y(&mut self, margin_fraction: Vec2) {
let height = self.height().max(0.0); let height = self.height().max(0.0);
self.expand_y(margin_fraction.y as f64 * height); self.expand_y(margin_fraction.y as f64 * height);
} }
pub(crate) fn range_x(&self) -> RangeInclusive<f64> { #[inline]
pub fn range_x(&self) -> RangeInclusive<f64> {
self.min[0]..=self.max[0] self.min[0]..=self.max[0]
} }
pub(crate) fn range_y(&self) -> RangeInclusive<f64> { #[inline]
pub fn range_y(&self) -> RangeInclusive<f64> {
self.min[1]..=self.max[1] self.min[1]..=self.max[1]
} }
pub(crate) fn make_x_symmetrical(&mut self) { #[inline]
pub 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(crate) fn make_y_symmetrical(&mut self) { #[inline]
pub 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;
@ -232,20 +266,23 @@ impl PlotTransform {
} }
/// ui-space rectangle. /// ui-space rectangle.
#[inline]
pub fn frame(&self) -> &Rect { pub fn frame(&self) -> &Rect {
&self.frame &self.frame
} }
/// Plot-space bounds. /// Plot-space bounds.
#[inline]
pub fn bounds(&self) -> &PlotBounds { pub fn bounds(&self) -> &PlotBounds {
&self.bounds &self.bounds
} }
pub(crate) fn set_bounds(&mut self, bounds: PlotBounds) { #[inline]
pub fn set_bounds(&mut self, bounds: PlotBounds) {
self.bounds = bounds; self.bounds = bounds;
} }
pub(crate) 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.;
} }
@ -258,7 +295,7 @@ impl PlotTransform {
} }
/// Zoom by a relative factor with the given screen position as center. /// Zoom by a relative factor with the given screen position as center.
pub(crate) fn zoom(&mut self, zoom_factor: Vec2, center: Pos2) { pub fn zoom(&mut self, zoom_factor: Vec2, center: Pos2) {
let center = self.value_from_position(center); let center = self.value_from_position(center);
let mut new_bounds = self.bounds; let mut new_bounds = self.bounds;