Skip to content

Commit ef55c29

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 d00fa85 commit ef55c29

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
@@ -5,8 +5,8 @@ use egui::{remap, vec2, Color32, ComboBox, NumExt, Pos2, Response, Stroke, TextW
55

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

1212
// ----------------------------------------------------------------------------
@@ -21,6 +21,7 @@ enum Panel {
2121
Interaction,
2222
CustomAxes,
2323
LinkedAxes,
24+
LogAxes,
2425
}
2526

2627
impl Default for Panel {
@@ -41,6 +42,7 @@ pub struct PlotDemo {
4142
interaction_demo: InteractionDemo,
4243
custom_axes_demo: CustomAxesDemo,
4344
linked_axes_demo: LinkedAxesDemo,
45+
log_axes_demo: LogAxesDemo,
4446
open_panel: Panel,
4547
}
4648

@@ -72,6 +74,7 @@ impl PlotDemo {
7274
ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction");
7375
ui.selectable_value(&mut self.open_panel, Panel::CustomAxes, "Custom Axes");
7476
ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes");
77+
ui.selectable_value(&mut self.open_panel, Panel::LogAxes, "Log Axes");
7578
});
7679
ui.separator();
7780

@@ -100,6 +103,9 @@ impl PlotDemo {
100103
Panel::LinkedAxes => {
101104
self.linked_axes_demo.ui(ui);
102105
}
106+
Panel::LogAxes => {
107+
self.log_axes_demo.ui(ui);
108+
}
103109
}
104110
}
105111
}
@@ -673,6 +679,76 @@ impl LinkedAxesDemo {
673679
}
674680
}
675681

682+
// ----------------------------------------------------------------------------
683+
#[derive(PartialEq, serde::Deserialize, serde::Serialize)]
684+
struct LogAxesDemo {
685+
log_axes: Vec2b,
686+
}
687+
688+
impl Default for LogAxesDemo {
689+
fn default() -> Self {
690+
Self {
691+
log_axes: Vec2b::new(false, true),
692+
}
693+
}
694+
}
695+
696+
impl LogAxesDemo {
697+
fn line_exp() -> Line {
698+
Line::new(PlotPoints::from_explicit_callback(
699+
move |x| 10.0_f64.powf(x / 200.0),
700+
0.1..=1000.0,
701+
1000,
702+
))
703+
.name("y = 10^(x/200)")
704+
.color(Color32::RED)
705+
}
706+
707+
fn line_lin() -> Line {
708+
Line::new(PlotPoints::from_explicit_callback(
709+
move |x| -5.0 + x,
710+
0.1..=1000.0,
711+
1000,
712+
))
713+
.name("y = -5 + x")
714+
.color(Color32::GREEN)
715+
}
716+
717+
fn line_log() -> Line {
718+
Line::new(PlotPoints::from_explicit_callback(
719+
move |x| x.log10(),
720+
0.1..=1000.0,
721+
1000,
722+
))
723+
.name("y = log10(x)")
724+
.color(Color32::BLUE)
725+
}
726+
727+
fn ui(&mut self, ui: &mut egui::Ui) -> Response {
728+
let just_changed = ui.checkbox(&mut self.log_axes.x, "Log X-Axis").clicked();
729+
ui.checkbox(&mut self.log_axes.y, "Log Y-Axis");
730+
Plot::new("log_demo")
731+
.log_axes(self.log_axes)
732+
.x_axis_label("x")
733+
.y_axis_label("y")
734+
.show_axes(Vec2b::new(true, true))
735+
.legend(Legend::default())
736+
.show(ui, |ui| {
737+
if just_changed {
738+
if self.log_axes.x {
739+
ui.set_plot_bounds(PlotBounds::from_min_max([0.1, 0.1], [1e3, 1e4]));
740+
} else {
741+
ui.set_plot_bounds(PlotBounds::from_min_max([0.0, 0.0], [3.0, 1000.0]));
742+
}
743+
}
744+
ui.line(Self::line_exp());
745+
ui.line(Self::line_lin());
746+
ui.line(Self::line_log());
747+
})
748+
.response
749+
}
750+
}
751+
676752
// ----------------------------------------------------------------------------
677753

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

egui_plot/src/axis.rs

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

328331
if spacing_in_points <= label_spacing.min {
329332
// 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
@@ -2060,7 +2060,7 @@ pub(super) fn rulers_at_value(
20602060
};
20612061

20622062
let text = {
2063-
let scale = plot.transform.dvalue_dpos();
2063+
let scale = plot.transform.smallest_distance_per_point();
20642064
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
20652065
let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
20662066
if let Some(custom_label) = label_formatter {

0 commit comments

Comments
 (0)