Fix plot auto bounds (#1865)

* Better estimate the plot bounds for generator functions

Avoid infinities, and sample more densely

* Optimize and improve plot auto-bounds logic

* Fix cropping out of the top/bottom of plots during auto-bounds
This commit is contained in:
Emil Ernerfeldt 2022-07-29 12:32:47 +02:00 committed by GitHub
parent 97880e18d7
commit 278db1c94b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 23 deletions

View file

@ -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 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 `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 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 ## 0.18.1 - 2022-05-01

View file

@ -378,15 +378,44 @@ pub struct ExplicitGenerator {
impl ExplicitGenerator { impl ExplicitGenerator {
fn estimate_bounds(&self) -> PlotBounds { 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 min_x = *self.x_range.start();
let max_x = *self.x_range.end(); let max_x = *self.x_range.end();
let min_y = (self.function)(min_x);
let max_y = (self.function)(max_x); add_x(min_x);
// TODO(emilk): sample some more points add_x(max_x);
PlotBounds {
min: [min_x, min_y], if min_x.is_finite() && max_x.is_finite() {
max: [max_x, max_y], // 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
} }
} }

View file

@ -682,8 +682,12 @@ impl Plot {
auto_bounds = true.into(); auto_bounds = true.into();
} }
if !bounds.is_valid() {
auto_bounds = true.into();
}
// Set bounds automatically based on content. // Set bounds automatically based on content.
if auto_bounds.any() || !bounds.is_valid() { if auto_bounds.any() {
if auto_bounds.x { if auto_bounds.x {
bounds.set_x(&min_auto_bounds); bounds.set_x(&min_auto_bounds);
} }
@ -693,13 +697,13 @@ impl Plot {
} }
for item in &items { for item in &items {
// bounds.merge(&item.get_bounds()); let item_bounds = item.get_bounds();
if auto_bounds.x { if auto_bounds.x {
bounds.merge_x(&item.get_bounds()); bounds.merge_x(&item_bounds);
} }
if auto_bounds.y { 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); 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 { if let Some(data_aspect) = data_aspect {
let preserve_y = linked_axes if let Some(linked_axes) = &linked_axes {
.as_ref() let change_x = linked_axes.link_y && !linked_axes.link_x;
.map_or(false, |group| group.link_y && !group.link_x); transform.set_aspect_by_changing_axis(data_aspect as f64, change_x);
transform.set_aspect(data_aspect as f64, preserve_y); } else {
transform.set_aspect_by_expanding(data_aspect as f64);
}
} }
// Dragging // Dragging
@ -766,7 +772,7 @@ impl Plot {
max: [box_end_pos.x, box_start_pos.y], max: [box_end_pos.x, box_start_pos.y],
}; };
if new_bounds.is_valid() { if new_bounds.is_valid() {
*transform.bounds_mut() = new_bounds; transform.set_bounds(new_bounds);
auto_bounds = false.into(); auto_bounds = false.into();
} else { } else {
auto_bounds = true.into(); auto_bounds = true.into();

View file

@ -206,8 +206,8 @@ impl ScreenTransform {
&self.bounds &self.bounds
} }
pub fn bounds_mut(&mut self) -> &mut PlotBounds { pub fn set_bounds(&mut self, bounds: PlotBounds) {
&mut self.bounds self.bounds = bounds;
} }
pub fn translate_bounds(&mut self, mut delta_pos: Vec2) { pub fn translate_bounds(&mut self, mut delta_pos: Vec2) {
@ -299,15 +299,17 @@ impl ScreenTransform {
[1.0 / self.dpos_dvalue_x(), 1.0 / self.dpos_dvalue_y()] [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 rw = self.frame.width() as f64;
let rh = self.frame.height() as f64; let rh = self.frame.height() as f64;
(self.bounds.width() / rw) / (self.bounds.height() / rh) (self.bounds.width() / rw) / (self.bounds.height() / rh)
} }
/// Sets the aspect ratio by either expanding the x-axis or contracting the y-axis. /// Sets the aspect ratio by expanding the x- or y-axis.
pub fn set_aspect(&mut self, aspect: f64, preserve_y: bool) { ///
let current_aspect = self.get_aspect(); /// 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; let epsilon = 1e-5;
if (current_aspect - aspect).abs() < epsilon { if (current_aspect - aspect).abs() < epsilon {
@ -315,7 +317,26 @@ impl ScreenTransform {
return; return;
} }
if preserve_y { 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 {
// Don't make any changes when the aspect is already almost correct.
return;
}
if change_x {
self.bounds self.bounds
.expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5); .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
} else { } else {