diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c5d4891776..139f44a8fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui-w * Fixed dead-lock when alt-tabbing while also showing a tooltip ([#1618](https://github.com/emilk/egui/pull/1618)). * Fixed `ScrollArea` scrolling when editing an unrelated `TextEdit` ([#1779](https://github.com/emilk/egui/pull/1779)). * Fixed focus behavior when pressing Tab in a UI with no focused widget ([#1861](https://github.com/emilk/egui/pull/1861)). +* Fixed automatic plot bounds ([#1865](https://github.com/emilk/egui/pull/1865)). ## 0.18.1 - 2022-05-01 diff --git a/egui/src/widgets/plot/items/values.rs b/egui/src/widgets/plot/items/values.rs index 3cfb845d1ec..d28ac008595 100644 --- a/egui/src/widgets/plot/items/values.rs +++ b/egui/src/widgets/plot/items/values.rs @@ -378,15 +378,44 @@ pub struct ExplicitGenerator { impl ExplicitGenerator { fn estimate_bounds(&self) -> PlotBounds { + let mut bounds = PlotBounds::NOTHING; + + let mut add_x = |x: f64| { + // avoid infinities, as we cannot auto-bound on them! + if x.is_finite() { + bounds.extend_with_x(x); + } + let y = (self.function)(x); + if y.is_finite() { + bounds.extend_with_y(y); + } + }; + let min_x = *self.x_range.start(); let max_x = *self.x_range.end(); - let min_y = (self.function)(min_x); - let max_y = (self.function)(max_x); - // TODO(emilk): sample some more points - PlotBounds { - min: [min_x, min_y], - max: [max_x, max_y], + + add_x(min_x); + add_x(max_x); + + if min_x.is_finite() && max_x.is_finite() { + // Sample some points in the interval: + const N: u32 = 8; + for i in 1..N { + let t = i as f64 / (N - 1) as f64; + let x = crate::lerp(min_x..=max_x, t); + add_x(x); + } + } else { + // Try adding some points anyway: + for x in [-1, 0, 1] { + let x = x as f64; + if min_x <= x && x <= max_x { + add_x(x); + } + } } + + bounds } } diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 911d846dacd..7695a05d82d 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -682,8 +682,12 @@ impl Plot { auto_bounds = true.into(); } + if !bounds.is_valid() { + auto_bounds = true.into(); + } + // Set bounds automatically based on content. - if auto_bounds.any() || !bounds.is_valid() { + if auto_bounds.any() { if auto_bounds.x { bounds.set_x(&min_auto_bounds); } @@ -693,13 +697,13 @@ impl Plot { } for item in &items { - // bounds.merge(&item.get_bounds()); + let item_bounds = item.get_bounds(); if auto_bounds.x { - bounds.merge_x(&item.get_bounds()); + bounds.merge_x(&item_bounds); } if auto_bounds.y { - bounds.merge_y(&item.get_bounds()); + bounds.merge_y(&item_bounds); } } @@ -714,12 +718,14 @@ impl Plot { let mut transform = ScreenTransform::new(rect, bounds, center_x_axis, center_y_axis); - // Enforce equal aspect ratio. + // Enforce aspect ratio if let Some(data_aspect) = data_aspect { - let preserve_y = linked_axes - .as_ref() - .map_or(false, |group| group.link_y && !group.link_x); - transform.set_aspect(data_aspect as f64, preserve_y); + if let Some(linked_axes) = &linked_axes { + let change_x = linked_axes.link_y && !linked_axes.link_x; + transform.set_aspect_by_changing_axis(data_aspect as f64, change_x); + } else { + transform.set_aspect_by_expanding(data_aspect as f64); + } } // Dragging @@ -766,7 +772,7 @@ impl Plot { max: [box_end_pos.x, box_start_pos.y], }; if new_bounds.is_valid() { - *transform.bounds_mut() = new_bounds; + transform.set_bounds(new_bounds); auto_bounds = false.into(); } else { auto_bounds = true.into(); diff --git a/egui/src/widgets/plot/transform.rs b/egui/src/widgets/plot/transform.rs index b5eb0cb2ac1..b4a77fbab58 100644 --- a/egui/src/widgets/plot/transform.rs +++ b/egui/src/widgets/plot/transform.rs @@ -206,8 +206,8 @@ impl ScreenTransform { &self.bounds } - pub fn bounds_mut(&mut self) -> &mut PlotBounds { - &mut self.bounds + pub fn set_bounds(&mut self, bounds: PlotBounds) { + self.bounds = bounds; } pub fn translate_bounds(&mut self, mut delta_pos: Vec2) { @@ -299,15 +299,36 @@ impl ScreenTransform { [1.0 / self.dpos_dvalue_x(), 1.0 / self.dpos_dvalue_y()] } - pub fn get_aspect(&self) -> f64 { + fn aspect(&self) -> f64 { let rw = self.frame.width() as f64; let rh = self.frame.height() as f64; (self.bounds.width() / rw) / (self.bounds.height() / rh) } - /// Sets the aspect ratio by either expanding the x-axis or contracting the y-axis. - pub fn set_aspect(&mut self, aspect: f64, preserve_y: bool) { - let current_aspect = self.get_aspect(); + /// Sets the aspect ratio by expanding the x- or y-axis. + /// + /// This never contracts, so we don't miss out on any data. + pub fn set_aspect_by_expanding(&mut self, aspect: f64) { + let current_aspect = self.aspect(); + + let epsilon = 1e-5; + if (current_aspect - aspect).abs() < epsilon { + // Don't make any changes when the aspect is already almost correct. + return; + } + + if current_aspect < aspect { + self.bounds + .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5); + } else { + self.bounds + .expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5); + } + } + + /// Sets the aspect ratio by changing either the X or Y axis (callers choice). + pub fn set_aspect_by_changing_axis(&mut self, aspect: f64, change_x: bool) { + let current_aspect = self.aspect(); let epsilon = 1e-5; if (current_aspect - aspect).abs() < epsilon { @@ -315,7 +336,7 @@ impl ScreenTransform { return; } - if preserve_y { + if change_x { self.bounds .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5); } else {