Skip to content

Commit

Permalink
Add logarithmic plot axes
Browse files Browse the repository at this point in the history
This commit is an initial implementation for adding logarithmic plotting
axis.

This very much needs more testing!

The basic idea is, that everything stays the same, but PlotTransform
does the much needed coordinate transformation for us.

That is, unfortunatley not all of the story.

 * In a lot of places, we need estimates of "how many pixels does 1 plot
   space unit take" and the likes, either for overdraw reduction, or
   generally to size things. PlotTransform has been modifed for that for
   now, so this should work.
 * While the normal grid spacer renders just fine, it will also casually
   try to generate 100s of thousands of lines for a bigger range log
   plot. So GridInput has been made aware if there is a log axis
   present. The default spacer has also been modified to work initially.
 * All of the PlotBound transformations within PlotTransform need to be
   aware and handle the log scaling properly. This is done and works
   well, but its a bit.. icky, for lack of a better word. If someone has
   a better idea how to handle this, be my guest :D

Especially the spacer generation is still kinda WIP; it is messy at best
right now. Especially for zooming in, it currently only adds it on the
lower bound due to the way the generator function works right now.

I will address this in a follow up commit (or someone else will).
  • Loading branch information
mkalte666 committed Dec 18, 2024
1 parent 6a7ff72 commit 6cd8528
Show file tree
Hide file tree
Showing 8 changed files with 463 additions and 90 deletions.
80 changes: 78 additions & 2 deletions demo/src/plot_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use egui::{

use egui_plot::{
Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner,
GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint,
PlotPoints, PlotResponse, Points, Polygon, Text, VLine,
GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotBounds, PlotImage,
PlotPoint, PlotPoints, PlotResponse, Points, Polygon, Text, VLine,
};

// ----------------------------------------------------------------------------
Expand All @@ -23,6 +23,7 @@ enum Panel {
Interaction,
CustomAxes,
LinkedAxes,
LogAxes,
}

impl Default for Panel {
Expand All @@ -43,6 +44,7 @@ pub struct PlotDemo {
interaction_demo: InteractionDemo,
custom_axes_demo: CustomAxesDemo,
linked_axes_demo: LinkedAxesDemo,
log_axes_demo: LogAxesDemo,
open_panel: Panel,
}

Expand Down Expand Up @@ -74,6 +76,7 @@ impl PlotDemo {
ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction");
ui.selectable_value(&mut self.open_panel, Panel::CustomAxes, "Custom Axes");
ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes");
ui.selectable_value(&mut self.open_panel, Panel::LogAxes, "Log Axes");
});
ui.separator();

Expand Down Expand Up @@ -102,6 +105,9 @@ impl PlotDemo {
Panel::LinkedAxes => {
self.linked_axes_demo.ui(ui);
}
Panel::LogAxes => {
self.log_axes_demo.ui(ui);
}
}
}
}
Expand Down Expand Up @@ -691,6 +697,76 @@ impl LinkedAxesDemo {
}
}

// ----------------------------------------------------------------------------
#[derive(PartialEq, serde::Deserialize, serde::Serialize)]
struct LogAxesDemo {
log_axes: Vec2b,
}

impl Default for LogAxesDemo {
fn default() -> Self {
Self {
log_axes: Vec2b::new(false, true),
}
}
}

impl LogAxesDemo {
fn line_exp() -> Line {
Line::new(PlotPoints::from_explicit_callback(
move |x| 10.0_f64.powf(x / 200.0),
0.1..=1000.0,
1000,
))
.name("y = 10^(x/200)")
.color(Color32::RED)
}

fn line_lin() -> Line {
Line::new(PlotPoints::from_explicit_callback(
move |x| -5.0 + x,
0.1..=1000.0,
1000,
))
.name("y = -5 + x")
.color(Color32::GREEN)
}

fn line_log() -> Line {
Line::new(PlotPoints::from_explicit_callback(
move |x| x.log10(),
0.1..=1000.0,
1000,
))
.name("y = log10(x)")
.color(Color32::BLUE)
}

fn ui(&mut self, ui: &mut egui::Ui) -> Response {
let just_changed = ui.checkbox(&mut self.log_axes.x, "Log X-Axis").clicked();
ui.checkbox(&mut self.log_axes.y, "Log Y-Axis");
Plot::new("log_demo")
.log_axes(self.log_axes)
.x_axis_label("x")
.y_axis_label("y")
.show_axes(Vec2b::new(true, true))
.legend(Legend::default())
.show(ui, |ui| {
if just_changed {
if self.log_axes.x {
ui.set_plot_bounds(PlotBounds::from_min_max([0.1, 0.1], [1e3, 1e4]));
} else {
ui.set_plot_bounds(PlotBounds::from_min_max([0.0, 0.0], [3.0, 1000.0]));
}
}
ui.line(Self::line_exp());
ui.line(Self::line_lin());
ui.line(Self::line_log());
})
.response
}
}

// ----------------------------------------------------------------------------

#[derive(Default, PartialEq, serde::Deserialize, serde::Serialize)]
Expand Down
7 changes: 5 additions & 2 deletions egui_plot/src/axis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,11 @@ impl<'a> AxisWidget<'a> {
for step in self.steps.iter() {
let text = (self.hints.formatter)(*step, &self.range);
if !text.is_empty() {
let spacing_in_points =
(transform.dpos_dvalue()[usize::from(axis)] * step.step_size).abs() as f32;
let spacing_in_points = transform.points_at_pos_range(
[step.value, step.value],
[step.step_size, step.step_size],
)[usize::from(axis)]
.abs();

if spacing_in_points <= label_spacing.min {
// Labels are too close together - don't paint them.
Expand Down
2 changes: 1 addition & 1 deletion egui_plot/src/items/bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ impl RectElement for Bar {
}

fn default_values_format(&self, transform: &PlotTransform) -> String {
let scale = transform.dvalue_dpos();
let scale = transform.smallest_distance_per_point();
let scale = match self.orientation {
Orientation::Horizontal => scale[0],
Orientation::Vertical => scale[1],
Expand Down
2 changes: 1 addition & 1 deletion egui_plot/src/items/box_elem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ impl RectElement for BoxElem {
}

fn default_values_format(&self, transform: &PlotTransform) -> String {
let scale = transform.dvalue_dpos();
let scale = transform.smallest_distance_per_point();
let scale = match self.orientation {
Orientation::Horizontal => scale[0],
Orientation::Vertical => scale[1],
Expand Down
2 changes: 1 addition & 1 deletion egui_plot/src/items/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2069,7 +2069,7 @@ pub(super) fn rulers_at_value(
};

let text = {
let scale = plot.transform.dvalue_dpos();
let scale = plot.transform.smallest_distance_per_point();
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
if let Some(custom_label) = label_formatter {
Expand Down
Loading

0 comments on commit 6cd8528

Please sign in to comment.