Skip to content

Commit

Permalink
Fix plot auto bounds (#1865)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
emilk committed Jul 29, 2022
1 parent 97880e1 commit 278db1c
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
41 changes: 35 additions & 6 deletions egui/src/widgets/plot/items/values.rs
Expand Up @@ -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
}
}

Expand Down
26 changes: 16 additions & 10 deletions egui/src/widgets/plot/mod.rs
Expand Up @@ -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);
}
Expand All @@ -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);
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
35 changes: 28 additions & 7 deletions egui/src/widgets/plot/transform.rs
Expand Up @@ -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) {
Expand Down Expand Up @@ -299,23 +299,44 @@ 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 {
// Don't make any changes when the aspect is already almost correct.
return;
}

if preserve_y {
if change_x {
self.bounds
.expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
} else {
Expand Down

0 comments on commit 278db1c

Please sign in to comment.