Skip to content

Commit 72e78a8

Browse files
committed
Add logarithmic plot axes
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).
1 parent e96f4e1 commit 72e78a8

File tree

8 files changed

+471
-90
lines changed

8 files changed

+471
-90
lines changed

demo/src/plot_demo.rs

+78-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use egui::{
77

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

1414
// ----------------------------------------------------------------------------
@@ -23,6 +23,7 @@ enum Panel {
2323
Interaction,
2424
CustomAxes,
2525
LinkedAxes,
26+
LogAxes,
2627
}
2728

2829
impl Default for Panel {
@@ -43,6 +44,7 @@ pub struct PlotDemo {
4344
interaction_demo: InteractionDemo,
4445
custom_axes_demo: CustomAxesDemo,
4546
linked_axes_demo: LinkedAxesDemo,
47+
log_axes_demo: LogAxesDemo,
4648
open_panel: Panel,
4749
}
4850

@@ -74,6 +76,7 @@ impl PlotDemo {
7476
ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction");
7577
ui.selectable_value(&mut self.open_panel, Panel::CustomAxes, "Custom Axes");
7678
ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes");
79+
ui.selectable_value(&mut self.open_panel, Panel::LogAxes, "Log Axes");
7780
});
7881
ui.separator();
7982

@@ -102,6 +105,9 @@ impl PlotDemo {
102105
Panel::LinkedAxes => {
103106
self.linked_axes_demo.ui(ui);
104107
}
108+
Panel::LogAxes => {
109+
self.log_axes_demo.ui(ui);
110+
}
105111
}
106112
}
107113
}
@@ -691,6 +697,76 @@ impl LinkedAxesDemo {
691697
}
692698
}
693699

700+
// ----------------------------------------------------------------------------
701+
#[derive(PartialEq, serde::Deserialize, serde::Serialize)]
702+
struct LogAxesDemo {
703+
log_axes: Vec2b,
704+
}
705+
706+
impl Default for LogAxesDemo {
707+
fn default() -> Self {
708+
Self {
709+
log_axes: Vec2b::new(false, true),
710+
}
711+
}
712+
}
713+
714+
impl LogAxesDemo {
715+
fn line_exp() -> Line {
716+
Line::new(PlotPoints::from_explicit_callback(
717+
move |x| 10.0_f64.powf(x / 200.0),
718+
0.1..=1000.0,
719+
1000,
720+
))
721+
.name("y = 10^(x/200)")
722+
.color(Color32::RED)
723+
}
724+
725+
fn line_lin() -> Line {
726+
Line::new(PlotPoints::from_explicit_callback(
727+
move |x| -5.0 + x,
728+
0.1..=1000.0,
729+
1000,
730+
))
731+
.name("y = -5 + x")
732+
.color(Color32::GREEN)
733+
}
734+
735+
fn line_log() -> Line {
736+
Line::new(PlotPoints::from_explicit_callback(
737+
move |x| x.log10(),
738+
0.1..=1000.0,
739+
1000,
740+
))
741+
.name("y = log10(x)")
742+
.color(Color32::BLUE)
743+
}
744+
745+
fn ui(&mut self, ui: &mut egui::Ui) -> Response {
746+
let just_changed = ui.checkbox(&mut self.log_axes.x, "Log X-Axis").clicked();
747+
ui.checkbox(&mut self.log_axes.y, "Log Y-Axis");
748+
Plot::new("log_demo")
749+
.log_axes(self.log_axes)
750+
.x_axis_label("x")
751+
.y_axis_label("y")
752+
.show_axes(Vec2b::new(true, true))
753+
.legend(Legend::default())
754+
.show(ui, |ui| {
755+
if just_changed {
756+
if self.log_axes.x {
757+
ui.set_plot_bounds(PlotBounds::from_min_max([0.1, 0.1], [1e3, 1e4]));
758+
} else {
759+
ui.set_plot_bounds(PlotBounds::from_min_max([0.0, 0.0], [3.0, 1000.0]));
760+
}
761+
}
762+
ui.line(Self::line_exp());
763+
ui.line(Self::line_lin());
764+
ui.line(Self::line_log());
765+
})
766+
.response
767+
}
768+
}
769+
694770
// ----------------------------------------------------------------------------
695771

696772
#[derive(Default, PartialEq, serde::Deserialize, serde::Serialize)]

egui_plot/src/axis.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,11 @@ impl<'a> AxisWidget<'a> {
325325
for step in self.steps.iter() {
326326
let text = (self.hints.formatter)(*step, &self.range);
327327
if !text.is_empty() {
328-
let spacing_in_points =
329-
(transform.dpos_dvalue()[usize::from(axis)] * step.step_size).abs() as f32;
328+
let spacing_in_points = transform.points_at_pos_range(
329+
[step.value, step.value],
330+
[step.step_size, step.step_size],
331+
)[usize::from(axis)]
332+
.abs();
330333

331334
if spacing_in_points <= label_spacing.min {
332335
// Labels are too close together - don't paint them.

egui_plot/src/items/bar.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl RectElement for Bar {
186186
}
187187

188188
fn default_values_format(&self, transform: &PlotTransform) -> String {
189-
let scale = transform.dvalue_dpos();
189+
let scale = transform.smallest_distance_per_point();
190190
let scale = match self.orientation {
191191
Orientation::Horizontal => scale[0],
192192
Orientation::Vertical => scale[1],

egui_plot/src/items/box_elem.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ impl RectElement for BoxElem {
271271
}
272272

273273
fn default_values_format(&self, transform: &PlotTransform) -> String {
274-
let scale = transform.dvalue_dpos();
274+
let scale = transform.smallest_distance_per_point();
275275
let scale = match self.orientation {
276276
Orientation::Horizontal => scale[0],
277277
Orientation::Vertical => scale[1],

egui_plot/src/items/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2069,7 +2069,7 @@ pub(super) fn rulers_at_value(
20692069
};
20702070

20712071
let text = {
2072-
let scale = plot.transform.dvalue_dpos();
2072+
let scale = plot.transform.smallest_distance_per_point();
20732073
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
20742074
let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
20752075
if let Some(custom_label) = label_formatter {

0 commit comments

Comments
 (0)