emilk / emilk/egui_plot

Log axes for plot

Open
#11 5 comments 7 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
468
Forks
108
Avg merge
15m
Merged PRs (30d)
3

Description

**Is your feature request related to a problem? Please describe.**
I want to be able to plot data on a logarithmic scale. Either just x, just y or both axes.

**Describe the solution you'd like**
It would be easiest to have an implementation that would only need one call in the constructor:
```rust
let my_plot = Plot::new("My Plot")
.height(height)
.width(width)
.log_axes([false, self.log_mode])
.legend(Legend::default());
```
to enable the log axes.

**Describe alternatives you've considered**
None

**Additional context**
I already started [here](https://github.com/hacknus/egui/tree/log_plot).

Bildschirm­foto 2023-03-04 um 23 56 16

Specifically, I started by adding the function
```rust
pub fn log_axes(mut self, log_axes: [bool; 2]) -> Self {
self.log_axes = log_axes;
if self.log_axes[0] && self.log_axes[1] {
let label_fmt = move |s: &str, val: &PlotPoint| {
format!(
"{}\n{:4.2}\n{:4.2}",
s,
10.0_f64.powf(val.x),
10.0_f64.powf(val.y)
)
};
let ax_fmt =
move |x: f64, _range: &RangeInclusive| format!("{:4.2}", 10.0_f64.powf(x));
self.label_formatter(label_fmt)
.x_axis_formatter(ax_fmt)
.y_axis_formatter(ax_fmt)
.x_grid_spacer(logarithmic_grid_spacer(10))
.y_grid_spacer(logarithmic_grid_spacer(10))
} else if self.log_axes[0] {
let label_fmt = move |s: &str, val: &PlotPoint| {
format!("{}\n{:4.2}\n{:4.2}", s, 10.0_f64.powf(val.x), val.y)
};
let ax_fmt =
move |y: f64, _range: &RangeInclusive| format!("{:4.2}", 10.0_f64.powf(y));
self.label_formatter(label_fmt)
.x_axis_formatter(ax_fmt)
.x_grid_spacer(logarithmic_grid_spacer(10))
} else if self.log_axes[1] {
let label_fmt = move |s: &str, val: &PlotPoint| {
format!("{}\n{:4.2}\n{:4.2}", s, val.x, 10.0_f64.powf(val.y),)
};
let ax_fmt =
move |y: f64, _range: &RangeInclusive| format!("{:4.2}", 10.0_f64.powf(y));
self.label_formatter(label_fmt)
.y_axis_formatter(ax_fmt)
.y_grid_spacer(logarithmic_grid_spacer(10))
} else {
self
}
}
```
which adds the correct formatters and adds a `logarithmic_grid_spacer`:
```rust
/// Fill in all values between [min, max]
fn generate_marks_log_plot(step_sizes: [f64; 3], bounds: (f64, f64)) -> Vec {
let mut steps = vec![];
make_marks_log_plot(&mut steps, step_sizes, bounds);
steps
}

/// Fill in all values between [min, max] which are a multiple of `step_size`
fn make_marks_log_plot(out: &mut Vec, step_size: [f64; 3], (min, max): (f64, f64)) {
assert!(max > min);
// TODO: pos/neg check
let first = (min).floor() as i64;
let last = (max).floor() as i64;

let mut marks_iter = vec![];
for i in first..=last {
let step = (10_f64.powi(i as i32 + 1) - 10_f64.powi(i as i32)) / 9.0;
marks_iter.push(GridMark {
value: i as f64,
step_size: step_size[1],
});
for j in 1..9 {
let value = 10_f64.powi(i as i32) + j as f64 * step;
marks_iter.push(GridMark {
value: value.log(10.0),
step_size: step_size[0],
});
}
}

out.extend(marks_iter);
}

pub fn logarithmic_grid_spacer(log_base: i64) -> GridSpacer {
let log_base = log_base as f64;
let step_sizes = move |input: GridInput| -> Vec {
// The distance between two of the thinnest grid lines is "rounded" up
// to the next-bigger power of base

let smallest_visible_unit = next_power(input.base_step_size, log_base);

let step_sizes = [
smallest_visible_unit,
smallest_visible_unit * log_base,
smallest_visible_unit * log_base * log_base,
];

generate_marks_log_plot(step_sizes, input.bounds)
};

Box::new(step_sizes)
}
```

**Still unfinished:**
So far I am stuck with these things:

- [ ] the `logarithmic_grid_spacer` fails to show new grid lines when we zoom in.
- [ ] negative values or zero values in the data are not treated/ignored.
- [ ] the origin (0,0) is shown in the plot at position (1,0) (I don't know how to turn that off), see screenshot above
- [ ] I have not found a way to transform the data when `self.log_axes[0]` or `self.log_axes[1]` are set to true in the library files. In the example, this has to be done manually before handing the data over to `Line::new()` or similar:
```rust
fn make_log_data(graph: &Vec<[f64; 2]>) -> Vec<[f64; 2]> {
graph
.iter()
.map(|v| {
if v[1] != 0.0 {
[v[0], v[1].log(10.0)]
} else {
[v[0], (v[1] + 0.01).log(10.0)] // offset, is 0.01 good?
}
})
.collect()
}
```
any help/inputs are appreciated!

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.