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

Log axes for plot #11

Open
4 tasks
hacknus opened this issue Mar 4, 2023 · 2 comments
Open
4 tasks

Log axes for plot #11

hacknus opened this issue Mar 4, 2023 · 2 comments

Comments

@hacknus
Copy link

hacknus commented Mar 4, 2023

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:

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.

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

Specifically, I started by adding the function

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<f64>| 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<f64>| 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<f64>| 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:

/// Fill in all values between [min, max]
fn generate_marks_log_plot(step_sizes: [f64; 3], bounds: (f64, f64)) -> Vec<GridMark> {
    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<GridMark>, 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<GridMark> {
        // 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:
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!

@emilk emilk transferred this issue from emilk/egui Jul 15, 2024
@mkalte666
Copy link

mkalte666 commented Jul 17, 2024

negative values or zero values in the data are not treated/ignored.

There are three solutions to this i think

  • Dont plot at all, perhaps report an error
  • Plot, but skip those values (can the lines plot handle gaps?)
  • Plot, but set those values to a user-povided minimum
    that should all be user-selectable.

I will have a go at this based on your PR @hacknus, as i need this fairly soon^^

@jordens
Copy link

jordens commented Aug 14, 2024

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

No branches or pull requests

3 participants