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

Improve plot grid appearance #2412

Merged
merged 8 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
### Added ⭐
* `Event::Key` now has a `repeat` field that is set to `true` if the event was the result of a key-repeat ([#2435](https://github.com/emilk/egui/pull/2435)).
* Add `Slider::drag_value_speed`, which lets you ask for finer precision when dragging the slider value rather than the actual slider.
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
* Add `Memory::any_popup_open`, which returns true if any popup is currently open ([#2464](https://github.com/emilk/egui/pull/2464)).

### Fixed 🐛
Expand Down
94 changes: 68 additions & 26 deletions crates/egui/src/widgets/plot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ pub struct Plot {
show_background: bool,
show_axes: [bool; 2],
grid_spacers: [GridSpacer; 2],
sharp_grid_lines: bool,
}

impl Plot {
Expand Down Expand Up @@ -331,6 +332,7 @@ impl Plot {
show_background: true,
show_axes: [true; 2],
grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)],
sharp_grid_lines: true,
}
}

Expand Down Expand Up @@ -617,6 +619,13 @@ impl Plot {
self
}

/// Round grid positions to full pixels to avoid aliasing. Improves plot appearance but might have an
/// undesired effect when shifting the plot bounds. Enabled by default.
pub fn sharp_grid_lines(mut self, enabled: bool) -> Self {
self.sharp_grid_lines = enabled;
self
}

/// Resets the plot.
pub fn reset(mut self) -> Self {
self.reset = true;
Expand Down Expand Up @@ -663,6 +672,7 @@ impl Plot {
linked_axes,
linked_cursors,
grid_spacers,
sharp_grid_lines,
} = self;

// Determine the size of the plot in the UI
Expand Down Expand Up @@ -965,6 +975,7 @@ impl Plot {
draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.link_x),
draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.link_y),
draw_cursors,
sharp_grid_lines,
};
let plot_cursors = prepared.ui(ui, &response);

Expand Down Expand Up @@ -1290,18 +1301,30 @@ struct PreparedPlot {
draw_cursor_x: bool,
draw_cursor_y: bool,
draw_cursors: Vec<Cursor>,
sharp_grid_lines: bool,
}

impl PreparedPlot {
fn ui(self, ui: &mut Ui, response: &Response) -> Vec<Cursor> {
let mut shapes = Vec::new();
let mut axes_shapes = Vec::new();

for d in 0..2 {
if self.show_axes[d] {
self.paint_axis(ui, d, &mut shapes);
self.paint_axis(
ui,
d,
self.show_axes[1 - d],
&mut axes_shapes,
self.sharp_grid_lines,
);
}
}

// Sort the axes by strength so that those with higher strength are drawn in front.
axes_shapes.sort_by(|(_, strength1), (_, strength2)| strength1.total_cmp(strength2));

let mut shapes = axes_shapes.into_iter().map(|(shape, _)| shape).collect();

let transform = &self.transform;

let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
Expand Down Expand Up @@ -1369,7 +1392,14 @@ impl PreparedPlot {
cursors
}

fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
fn paint_axis(
&self,
ui: &Ui,
axis: usize,
other_axis_shown: bool,
shapes: &mut Vec<(Shape, f32)>,
sharp_grid_lines: bool,
) {
let Self {
transform,
axis_formatters,
Expand Down Expand Up @@ -1408,38 +1438,47 @@ impl PreparedPlot {
let pos_in_gui = transform.position_from_point(&value);
let spacing_in_points = (transform.dpos_dvalue()[axis] * step.step_size).abs() as f32;

let line_alpha = remap_clamp(
spacing_in_points,
(MIN_LINE_SPACING_IN_POINTS as f32)..=300.0,
0.0..=0.15,
);
if spacing_in_points > MIN_LINE_SPACING_IN_POINTS as f32 {
let line_strength = remap_clamp(
spacing_in_points,
MIN_LINE_SPACING_IN_POINTS as f32..=300.0,
0.0..=1.0,
);

if line_alpha > 0.0 {
let line_color = color_from_alpha(ui, line_alpha);
let line_color = color_from_contrast(ui, line_strength);

let mut p0 = pos_in_gui;
let mut p1 = pos_in_gui;
p0[1 - axis] = transform.frame().min[1 - axis];
p1[1 - axis] = transform.frame().max[1 - axis];
// Round to avoid aliasing
p0 = ui.ctx().round_pos_to_pixels(p0);
p1 = ui.ctx().round_pos_to_pixels(p1);
shapes.push(Shape::line_segment([p0, p1], Stroke::new(1.0, line_color)));
if sharp_grid_lines {
// Round to avoid aliasing
p0 = ui.ctx().round_pos_to_pixels(p0);
p1 = ui.ctx().round_pos_to_pixels(p1);
}
shapes.push((
Shape::line_segment([p0, p1], Stroke::new(1.0, line_color)),
line_strength,
));
}

let text_alpha = remap_clamp(spacing_in_points, 40.0..=150.0, 0.0..=0.4);

if text_alpha > 0.0 {
let color = color_from_alpha(ui, text_alpha);
const MIN_TEXT_SPACING: f32 = 40.0;
if spacing_in_points > MIN_TEXT_SPACING {
let text_strength =
remap_clamp(spacing_in_points, MIN_TEXT_SPACING..=150.0, 0.0..=1.0);
let color = color_from_contrast(ui, text_strength);

let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() {
formatter(value_main, &axis_range)
} else {
emath::round_to_decimals(value_main, 5).to_string() // hack
};

// Skip origin label for y-axis if x-axis is already showing it (otherwise displayed twice)
let skip_origin_y = axis == 1 && other_axis_shown && value_main == 0.0;

// Custom formatters can return empty string to signal "no label at this resolution"
if !text.is_empty() {
if !text.is_empty() && !skip_origin_y {
let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color);

let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
Expand All @@ -1449,17 +1488,20 @@ impl PreparedPlot {
.at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0)
.at_least(transform.frame().min[1 - axis] + 1.0);

shapes.push(Shape::galley(text_pos, galley));
shapes.push((Shape::galley(text_pos, galley), text_strength));
}
}
}

fn color_from_alpha(ui: &Ui, alpha: f32) -> Color32 {
if ui.visuals().dark_mode {
Rgba::from_white_alpha(alpha).into()
} else {
Rgba::from_black_alpha((4.0 * alpha).at_most(1.0)).into()
}
fn color_from_contrast(ui: &Ui, contrast: f32) -> Color32 {
let bg = ui.visuals().extreme_bg_color;
let fg = ui.visuals().widgets.open.fg_stroke.color;
let mix = 0.5 * contrast.sqrt();
Color32::from_rgb(
lerp((bg.r() as f32)..=(fg.r() as f32), mix) as u8,
lerp((bg.g() as f32)..=(fg.g() as f32), mix) as u8,
lerp((bg.b() as f32)..=(fg.b() as f32), mix) as u8,
)
}
}

Expand Down