Skip to main content

egui_plot/
bounds.rs

1use std::ops::RangeInclusive;
2
3use ahash::HashMap;
4use egui::Id;
5use emath::Pos2;
6use emath::Vec2;
7use emath::Vec2b;
8
9/// A point coordinate in the plot.
10///
11/// Uses f64 for improved accuracy to enable plotting
12/// large values (e.g. unix time on x axis).
13#[derive(Clone, Copy, Debug, PartialEq)]
14pub struct PlotPoint {
15    /// This is often something monotonically increasing, such as time, but
16    /// doesn't have to be. Goes from left to right.
17    pub x: f64,
18
19    /// Goes from bottom to top (inverse of everything else in egui!).
20    pub y: f64,
21}
22
23impl From<[f64; 2]> for PlotPoint {
24    #[inline]
25    fn from([x, y]: [f64; 2]) -> Self {
26        Self { x, y }
27    }
28}
29
30impl PlotPoint {
31    #[inline(always)]
32    pub fn new(x: impl Into<f64>, y: impl Into<f64>) -> Self {
33        Self {
34            x: x.into(),
35            y: y.into(),
36        }
37    }
38
39    #[inline(always)]
40    pub fn to_pos2(self) -> Pos2 {
41        Pos2::new(self.x as f32, self.y as f32)
42    }
43
44    #[inline(always)]
45    pub fn to_vec2(self) -> Vec2 {
46        Vec2::new(self.x as f32, self.y as f32)
47    }
48}
49
50/// 2D bounding box of f64 precision.
51///
52/// The range of data values we show.
53#[derive(Clone, Copy, PartialEq, Debug)]
54#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
55pub struct PlotBounds {
56    pub(crate) min: [f64; 2],
57    pub(crate) max: [f64; 2],
58}
59
60impl PlotBounds {
61    pub const NOTHING: Self = Self {
62        min: [f64::INFINITY; 2],
63        max: [-f64::INFINITY; 2],
64    };
65
66    #[inline]
67    pub fn from_min_max(min: [f64; 2], max: [f64; 2]) -> Self {
68        Self { min, max }
69    }
70
71    #[inline]
72    pub fn min(&self) -> [f64; 2] {
73        self.min
74    }
75
76    #[inline]
77    pub fn max(&self) -> [f64; 2] {
78        self.max
79    }
80
81    #[inline]
82    pub fn new_symmetrical(half_extent: f64) -> Self {
83        Self {
84            min: [-half_extent; 2],
85            max: [half_extent; 2],
86        }
87    }
88
89    #[inline]
90    pub fn is_finite(&self) -> bool {
91        self.min[0].is_finite() && self.min[1].is_finite() && self.max[0].is_finite() && self.max[1].is_finite()
92    }
93
94    #[inline]
95    pub fn is_finite_x(&self) -> bool {
96        self.min[0].is_finite() && self.max[0].is_finite()
97    }
98
99    #[inline]
100    pub fn is_finite_y(&self) -> bool {
101        self.min[1].is_finite() && self.max[1].is_finite()
102    }
103
104    #[inline]
105    pub fn is_valid(&self) -> bool {
106        self.is_finite() && self.width() > 0.0 && self.height() > 0.0
107    }
108
109    #[inline]
110    pub fn is_valid_x(&self) -> bool {
111        self.is_finite_x() && self.width() > 0.0
112    }
113
114    #[inline]
115    pub fn is_valid_y(&self) -> bool {
116        self.is_finite_y() && self.height() > 0.0
117    }
118
119    #[inline]
120    pub fn width(&self) -> f64 {
121        self.max[0] - self.min[0]
122    }
123
124    #[inline]
125    pub fn height(&self) -> f64 {
126        self.max[1] - self.min[1]
127    }
128
129    #[inline]
130    pub fn center(&self) -> PlotPoint {
131        [
132            emath::fast_midpoint(self.min[0], self.max[0]),
133            emath::fast_midpoint(self.min[1], self.max[1]),
134        ]
135        .into()
136    }
137
138    /// Expand to include the given (x,y) value
139    #[inline]
140    pub fn extend_with(&mut self, value: &PlotPoint) {
141        self.extend_with_x(value.x);
142        self.extend_with_y(value.y);
143    }
144
145    /// Expand to include the given x coordinate
146    #[inline]
147    pub fn extend_with_x(&mut self, x: f64) {
148        self.min[0] = self.min[0].min(x);
149        self.max[0] = self.max[0].max(x);
150    }
151
152    /// Expand to include the given y coordinate
153    #[inline]
154    pub fn extend_with_y(&mut self, y: f64) {
155        self.min[1] = self.min[1].min(y);
156        self.max[1] = self.max[1].max(y);
157    }
158
159    #[inline]
160    fn clamp_to_finite(&mut self) {
161        for d in 0..2 {
162            self.min[d] = self.min[d].clamp(f64::MIN, f64::MAX);
163            if self.min[d].is_nan() {
164                self.min[d] = 0.0;
165            }
166
167            self.max[d] = self.max[d].clamp(f64::MIN, f64::MAX);
168            if self.max[d].is_nan() {
169                self.max[d] = 0.0;
170            }
171        }
172    }
173
174    #[inline]
175    pub fn expand_x(&mut self, pad: f64) {
176        if pad.is_finite() {
177            self.min[0] -= pad;
178            self.max[0] += pad;
179            self.clamp_to_finite();
180        }
181    }
182
183    #[inline]
184    pub fn expand_y(&mut self, pad: f64) {
185        if pad.is_finite() {
186            self.min[1] -= pad;
187            self.max[1] += pad;
188            self.clamp_to_finite();
189        }
190    }
191
192    #[inline]
193    pub fn merge_x(&mut self, other: &Self) {
194        self.min[0] = self.min[0].min(other.min[0]);
195        self.max[0] = self.max[0].max(other.max[0]);
196    }
197
198    #[inline]
199    pub fn merge_y(&mut self, other: &Self) {
200        self.min[1] = self.min[1].min(other.min[1]);
201        self.max[1] = self.max[1].max(other.max[1]);
202    }
203
204    #[inline]
205    pub fn set_x(&mut self, other: &Self) {
206        self.min[0] = other.min[0];
207        self.max[0] = other.max[0];
208    }
209
210    #[inline]
211    pub fn set_x_center_width(&mut self, x: f64, width: f64) {
212        self.min[0] = x - width / 2.0;
213        self.max[0] = x + width / 2.0;
214    }
215
216    #[inline]
217    pub fn set_y(&mut self, other: &Self) {
218        self.min[1] = other.min[1];
219        self.max[1] = other.max[1];
220    }
221
222    #[inline]
223    pub fn set_y_center_height(&mut self, y: f64, height: f64) {
224        self.min[1] = y - height / 2.0;
225        self.max[1] = y + height / 2.0;
226    }
227
228    #[inline]
229    pub fn merge(&mut self, other: &Self) {
230        self.min[0] = self.min[0].min(other.min[0]);
231        self.min[1] = self.min[1].min(other.min[1]);
232        self.max[0] = self.max[0].max(other.max[0]);
233        self.max[1] = self.max[1].max(other.max[1]);
234    }
235
236    #[inline]
237    pub fn translate_x(&mut self, delta: f64) {
238        if delta.is_finite() {
239            self.min[0] += delta;
240            self.max[0] += delta;
241            self.clamp_to_finite();
242        }
243    }
244
245    #[inline]
246    pub fn translate_y(&mut self, delta: f64) {
247        if delta.is_finite() {
248            self.min[1] += delta;
249            self.max[1] += delta;
250            self.clamp_to_finite();
251        }
252    }
253
254    #[inline]
255    pub fn translate(&mut self, delta: (f64, f64)) {
256        self.translate_x(delta.0);
257        self.translate_y(delta.1);
258    }
259
260    #[inline]
261    pub fn zoom(&mut self, zoom_factor: Vec2, center: PlotPoint) {
262        self.min[0] = center.x + (self.min[0] - center.x) / (zoom_factor.x as f64);
263        self.max[0] = center.x + (self.max[0] - center.x) / (zoom_factor.x as f64);
264        self.min[1] = center.y + (self.min[1] - center.y) / (zoom_factor.y as f64);
265        self.max[1] = center.y + (self.max[1] - center.y) / (zoom_factor.y as f64);
266    }
267
268    #[inline]
269    pub fn add_relative_margin_x(&mut self, margin_fraction: Vec2) {
270        let width = self.width().max(0.0);
271        self.expand_x(margin_fraction.x as f64 * width);
272    }
273
274    #[inline]
275    pub fn add_relative_margin_y(&mut self, margin_fraction: Vec2) {
276        let height = self.height().max(0.0);
277        self.expand_y(margin_fraction.y as f64 * height);
278    }
279
280    #[inline]
281    pub fn range_x(&self) -> RangeInclusive<f64> {
282        self.min[0]..=self.max[0]
283    }
284
285    #[inline]
286    pub fn range_y(&self) -> RangeInclusive<f64> {
287        self.min[1]..=self.max[1]
288    }
289
290    #[inline]
291    pub fn make_x_symmetrical(&mut self) {
292        let x_abs = self.min[0].abs().max(self.max[0].abs());
293        self.min[0] = -x_abs;
294        self.max[0] = x_abs;
295    }
296
297    #[inline]
298    pub fn make_y_symmetrical(&mut self) {
299        let y_abs = self.min[1].abs().max(self.max[1].abs());
300        self.min[1] = -y_abs;
301        self.max[1] = y_abs;
302    }
303}
304
305#[derive(Clone)]
306pub struct LinkedBounds {
307    pub bounds: PlotBounds,
308    pub auto_bounds: Vec2b,
309}
310
311#[derive(Default, Clone)]
312pub struct BoundsLinkGroups(pub HashMap<Id, LinkedBounds>);
313
314/// User-requested modifications to the plot bounds. We collect them in the plot
315/// build function to later apply them at the right time, as other modifications
316/// need to happen first.
317pub enum BoundsModification {
318    SetX(RangeInclusive<f64>),
319    SetY(RangeInclusive<f64>),
320    Translate(Vec2),
321    AutoBounds(Vec2b),
322    Zoom(Vec2, PlotPoint),
323}