Skip to content
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

Closed
wants to merge 9 commits into from

Conversation

YgorSouza
Copy link
Contributor

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.

@YgorSouza YgorSouza force-pushed the plot-log-scale-example branch from 9710f70 to 6233339 Compare May 29, 2024 20:43
Comment on lines +105 to +106
let x = if log_x { x.log10() } else { x };
let y = if log_y { y.log10() } else { y };
Copy link

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

Copy link
Contributor Author

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.

Copy link

@hyumo hyumo Jun 1, 2024

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

Copy link
Contributor Author

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.

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 {
Copy link
Contributor Author

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.

Copy link

@ForsakenHarmony ForsakenHarmony Jun 13, 2024

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)
}

Copy link
Contributor Author

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.

@foxcob
Copy link

foxcob commented Jul 9, 2024

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?

@YgorSouza
Copy link
Contributor Author

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.

@YgorSouza YgorSouza force-pushed the plot-log-scale-example branch from d3dc6ad to a137fb0 Compare July 9, 2024 20:10
@emilk
Copy link
Owner

emilk commented Jul 15, 2024

egui_plot has recently been moved to its own repository, at https://github.com/emilk/egui_plot

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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants