-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Example: semi-log and log-log plots #4564
Conversation
9710f70
to
6233339
Compare
let x = if log_x { x.log10() } else { x }; | ||
let y = if log_y { y.log10() } else { y }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when x or y is negative, log10
is not valid and returns NaN
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, normally you can't represent non-positive numbers on a log scale. Apparently there is a modified transform that allows that, but I think this starts to get too complicated for this example.
It is also noteworthy that the infinity value produced when we take the log of 0 causes the autobounds to stop working (it does not seem to be bothered by the NaNs though), so that is something that could be looked into, but in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I am basically trying to do a bode plot, I will just do a semi-log! Thanks a lot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for a Bode plot the y axis is in dB, so you don't need the log scale for it.
examples/plot_log_scale/src/main.rs
Outdated
fn log_axis_spacer(input: GridInput) -> Vec<GridMark> { | ||
let (min, max) = input.bounds; | ||
let mut marks = vec![]; | ||
for i in min.floor() as i32..=max.ceil() as i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the only thing missing is to make this spacer more general. Right now it only works well if the number of decades visible on the axis is between 0.5 and 15, roughly. It is a decent range for a log plot, but it would be nice if it could have the "infinite" zoom-in and zoom-out like the default grid. I haven't been able to figure that out yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had implemented it on top of the original plot, here's what I came up with
pub fn log_grid_spacer(log_base: i64) -> GridSpacer {
let log_base = log_base as f64;
let step_sizes = move |input: GridInput| -> Vec<GridMark> {
// The distance between two of the thinnest grid lines is "rounded" up
// to the next-bigger power of base
if input.log {
let smallest_power = last_power(input.bounds.0, log_base)
.log(log_base)
.ceil()
.max(0.0) as i32;
let largest_power = next_power(input.bounds.1, log_base).log(log_base).ceil() as i32;
let mut steps = vec![];
for power in smallest_power..largest_power {
let step_size = log_base.powi(power);
let next_step_size = log_base.powi(power + 1);
let bounds = (
step_size.max(input.bounds.0),
next_step_size.min(input.bounds.1),
);
if bounds.0 >= bounds.1 {
dbg!("oopsie");
}
fill_marks_between(&mut steps, step_size, bounds);
}
steps
} else {
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(step_sizes, input.bounds)
}
};
Box::new(step_sizes)
}
fn next_power(value: f64, base: f64) -> f64 {
assert_ne!(value, 0.0); // can be negative (typical for Y axis)
base.powi(value.abs().log(base).ceil() as i32)
}
fn last_power(value: f64, base: f64) -> f64 {
assert_ne!(value, 0.0); // can be negative (typical for Y axis)
base.powi(value.abs().log(base).floor() as i32)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'll see if I can adapt this to what I was trying to do (since some of these egui_plot functions are private). I'm still not sure if this is better off as an example or as part of the API. Either way feels more complicated than it should be with all the options people might need.
This is something I was considering writing myself, but I was looking at a way to map the pixel coordinates to value through an arbitrary function (and back). This accomplishes much of the same thing while keeping the actual plot linear. What's your estimate on this being ready? |
I'll see if I can look into it again this weekend. I think it's really only missing the grid. I was trying to come up with some code that is simple enough to be a useful example, but the code that generalizes to any range and zoom level is kind of hard to read and reason about. Also, egui_plot is kind of in a limbo at the moment as it looks for new maintainers, so maybe it's better not to make too many PRs until that is sorted out. |
d3dc6ad
to
a137fb0
Compare
This will hopefully speed up its development by having more reviewers and maintainers. Please re-open this PR at https://github.com/emilk/egui_plot/pulls See also: |
This has been requested a few times, such as emilk/egui_plot#11 and #4553.
As it is difficult to come up with an API that is clear and covers everyone's needs, I opted for an example for now, so people can modify it as they please. Eventually parts of the example can be moved to the egui_plot crate if desired.