How to draw egui::Painter objects within Plot.show ?
- Dominant language
- Rust
- Stars
- 468
- Forks
- 108
- Avg merge
- 15m
- Merged PRs (30d)
- 3
Description
Hey Everyone
First up, I'm new to Rust ( 2 weeks) + egui (1 week) , so apologies in advance for any and all stupidity forthcoming :-)
I am developing some plotting code using egui_plot and I want, if possible, to draw some egui::Painter objects within plot.show() rather than before or after plot.show(). Why do I want to do this:
- I want to draw rect objects. I don't see this capability easily within plot code. Hence the move to Painter
- I want to draw these objects at plot coordinates rather than screen coordinates. I'm not sure if there is access to plot coordinates from outside plot.show()?
- I want to control the draw order of these non-plot objects i.e. draw some behind some plot objects but in front of others.
### Questions:
Question 1:
How to access Painter from within plot.show() context? My lack of Rust knowledge is really limiting even how I describe this issue. From outside plot.show() I use ui.painter() but that idea fails from within plot.show() with the following error which I don't know how to solve:
> 46 | plot.show(ui, |plot_ui| {
> | ^ ---- --------- immutable borrow occurs here
> | | |
> | _____| immutable borrow later used by call
> | |
> ... |
> 62 | | let painter = ui.painter();
> | | -- first borrow occurs due to use of `*ui` in closure
> ... |
> 79 | | );
> 80 | | });
> | |______^ mutable borrow occurs here
So, I came across this instead:
` let painter = plot_ui.ctx().debug_painter();`
And that works. I can create a rectangular area in plot space using plot_ui.screen_from_plot() etc. and then call painter.rect() to draw it which works up as a demo at least. But I'm sure it is not the call I should be using for general use. One thing I believe debug_painter() does is, because it's a debug function, draw on top of everything else?
Question 2:
Is there access to plot coordinates from outside plot.show()? (If I had access, there would be less pressing need to draw Painter objects within plot.show()
Question 3:
Is there any way to control ordering of plot objects (and egui objects in general) apart from the implicit "back to front ordering" which I presume is how it functions regularly in code like this i.e the 'line' is drawn on top of the two 'vline's here:
```
plot_ui.vline(VLine::new("Lines vertical", 9.0));
plot_ui.vline(VLine::new("Lines vertical", -9.0));
plot_ui.line(line.name("Line with fill").id("line_with_fill"));
```
That's all the questions for now haha. Any answers or places to go to get answers would be most welcome. Here is the full code if you want to run it. It's a single 'main.rs' file, very simple code:
```
use eframe::egui;
use egui::{
Color32, Pos2, Rect, Stroke,
epaint::{CornerRadius, StrokeKind},
};
use egui_plot::{Legend, Plot, PlotPoint};
#[derive(Default)]
struct MyApp {
// random_nonsense: f32,
}
impl eframe::App for MyApp {
/// Called each time the UI needs repainting, which may be many times per second.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
candlestick_chart(ui);
ui.label("Central Panel please");
});
}
}
pub fn candlestick_chart(ui: &mut egui::Ui) {
let plot = Plot::new("candlestick chart")
.legend(Legend::default())
.cursor_color(Color32::DEBUG_COLOR)
.show_background(true)
.x_axis_label("Time".to_string())
.y_axis_label("Price".to_string())
.show_grid(false)
.default_x_bounds(0.0, 10.0) // Putting these in definitely affects screen_from_plot calls.....
.default_y_bounds(0.0, 10.0)
.allow_zoom(true)
.width(2000.) // Screen space or plot space?
.height(800.) // Screen space or plot space?
// .view_aspect(2.0);
;
let mut screen_rect: Rect = Rect::NOTHING;
plot.show(ui, |plot_ui| {
// define a rectangle in plot coordinates and then transform to screen coordinate using plot_ui.screen_from_plot() in order to render it.
// But have to put the y=5.0 in the min_plot_point, not sure why. How can min_y > max_y? Did I read somewhere y values were swapped over automatically somewhere?
let min_plot_point: PlotPoint = PlotPoint::new(1.0, 5.0);
let max_plot_point: PlotPoint = PlotPoint::new(5.0, 0.0);
println!("plt coords are min: {:?} max {:?}", min_plot_point, max_plot_point);
let screen_min: Pos2 = plot_ui.screen_from_plot(min_plot_point);
let screen_max: Pos2 = plot_ui.screen_from_plot(max_plot_point);
screen_rect = Rect::from_min_max(screen_min, screen_max); // Transform to screen coords
// Access a Painter: Good except...
// 1) y ordinates are reversed (don't understand), and
// 2) it uses plot_ui.ct().debug_painter() because I don't know how else to access it. This has effect of drawing on top of everything else. How else can I access Painter in this context?
let painter = plot_ui.ctx().debug_painter();
// Draw a rectangle at place on screen converted from plot_points
println!("The rect converted from plot coordinates to screen coordinates is being drawn at the following screen coordinates : {:?}", screen_rect);
painter.rect(
screen_rect,
CornerRadius::default(),
Color32::GREEN,
Stroke::new(122.2, Color32::BLUE),
StrokeKind::Inside,
);
// Draw a filled rectangle using screen coordinates not plot coordinates.
// Since screen y coords start from top of screen, the low 'y' value here showing at top of plot is correct...
painter.rect_filled(
Rect::from_min_max(Pos2::new(500.0, 50.0), Pos2::new(550.0, 150.0)),
10.0, // Corner radius
Color32::from_rgb(255, 100, 100), // Fill color (salmon pink)
);
});
println!("Preserved screen rectangle is: {:?}", screen_rect);
// try drawing a painter object on top of the plot using 'preserved screen coordinates'
// It works fine. And in this context, able to use ui.painter()
// question is why can I use ui.painter() here not but within the plot.show() above?
ui.painter().rect_filled(
screen_rect.expand(20.), // Expand rectangle size we preserved earlier. If we don't increase, we never get to see it coz debug_painter() always draws on top
5.55,
Color32::PURPLE,
);
}
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions::default();
eframe::run_native(
"Plot an egui::Painter object inside an egui::Plot::show()",
options,
Box::new(|_cc| Ok(Box::new(MyApp::default()))),
)
}
```
Contributor guide
Assessment
This issue has not been assessed yet.