Added support for linked axes. See comments.
This commit adds support for linked axes between plots. One can specify such linked limits using the new `linked_x_axis()` function (and the variants for the Y axes) by specifying an `Rc<RefCell<ImPlotRange>>` value, and passing clones of the same `Rc` to other plots. The values within those `Rc` need to be kept persistent between frames, hence the way to use this functionality is to keep a clone of the `Rc` outside the frame-drawing function as part of the application state. The regular limit setting API is unaffected.
This commit is contained in:
parent
ad80781f4d
commit
06cc3061c1
5 changed files with 570 additions and 391 deletions
|
@ -9,44 +9,64 @@ pub mod text_plots;
|
||||||
use imgui::{im_str, Condition, Ui, Window};
|
use imgui::{im_str, Condition, Ui, Window};
|
||||||
use implot::PlotUi;
|
use implot::PlotUi;
|
||||||
|
|
||||||
pub fn show_demos(ui: &Ui, plot_ui: &PlotUi) {
|
/// State of the demo code
|
||||||
Window::new(im_str!("implot-rs demo"))
|
pub struct DemoState {
|
||||||
.size([430.0, 450.0], Condition::FirstUseEver)
|
/// State of the line plots demo
|
||||||
.build(ui, || {
|
line_plots: line_plots::LinePlotDemoState,
|
||||||
ui.text(im_str!("Hello from implot-rs!"));
|
}
|
||||||
ui.text_wrapped(im_str!(
|
|
||||||
"The headers here demo the plotting features of the library.\
|
impl DemoState {
|
||||||
|
/// Create a new demo code state object with default values in it.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
line_plots: line_plots::LinePlotDemoState::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show all the demos
|
||||||
|
pub fn show_demos(&mut self, ui: &Ui, plot_ui: &PlotUi) {
|
||||||
|
// Most of the demos are currently still stateless, so the code here mostly just calls into
|
||||||
|
// the modules. The line plots demo is stateful though. Things will be refactored soon to
|
||||||
|
// make all the individual demos stateful to unify things more.
|
||||||
|
Window::new(im_str!("implot-rs demo"))
|
||||||
|
.size([430.0, 450.0], Condition::FirstUseEver)
|
||||||
|
.build(ui, || {
|
||||||
|
ui.text(im_str!("Hello from implot-rs!"));
|
||||||
|
ui.text_wrapped(im_str!(
|
||||||
|
"The headers here demo the plotting features of the library.\
|
||||||
Have a look at the example source code to see how they are implemented.\n\
|
Have a look at the example source code to see how they are implemented.\n\
|
||||||
Check out the demo from ImPlot itself first for instructions on how to\
|
Check out the demo from ImPlot itself first for instructions on how to\
|
||||||
interact with ImPlot plots."
|
interact with ImPlot plots."
|
||||||
));
|
));
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.text(im_str!("Bar plots:"));
|
ui.text(im_str!("Bar plots:"));
|
||||||
bar_plots::show_demo_headers(ui, plot_ui);
|
bar_plots::show_demo_headers(ui, plot_ui);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.text(im_str!("Line plots:"));
|
ui.text(im_str!("Line plots:"));
|
||||||
line_plots::show_demo_headers(ui, plot_ui);
|
// The line plots demo is stateful
|
||||||
|
self.line_plots.show_demo_headers(ui, plot_ui);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.text(im_str!("Scatter plots:"));
|
ui.text(im_str!("Scatter plots:"));
|
||||||
scatter_plots::show_demo_headers(ui, plot_ui);
|
scatter_plots::show_demo_headers(ui, plot_ui);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.text(im_str!("Text plots:"));
|
ui.text(im_str!("Text plots:"));
|
||||||
text_plots::show_demo_headers(ui, plot_ui);
|
text_plots::show_demo_headers(ui, plot_ui);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.text(im_str!("Stairs plots:"));
|
ui.text(im_str!("Stairs plots:"));
|
||||||
stairs_plots::show_demo_headers(ui, plot_ui);
|
stairs_plots::show_demo_headers(ui, plot_ui);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.text(im_str!("Heatmaps:"));
|
ui.text(im_str!("Heatmaps:"));
|
||||||
heatmaps::show_demo_headers(ui, plot_ui);
|
heatmaps::show_demo_headers(ui, plot_ui);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.text(im_str!("Stem plots:"));
|
ui.text(im_str!("Stem plots:"));
|
||||||
stem_plots::show_demo_headers(ui, plot_ui);
|
stem_plots::show_demo_headers(ui, plot_ui);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,367 +11,413 @@ use implot::{
|
||||||
StyleVar, YAxisChoice,
|
StyleVar, YAxisChoice,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn show_basic_plot(ui: &Ui, plot_ui: &PlotUi) {
|
use std::{cell::RefCell, rc::Rc};
|
||||||
ui.text(im_str!(
|
|
||||||
"This header just plots a line with as little code as possible."
|
/// State of the line plots demo.
|
||||||
));
|
pub struct LinePlotDemoState {
|
||||||
let content_width = ui.window_content_region_width();
|
linked_limits: Rc<RefCell<ImPlotRange>>,
|
||||||
Plot::new("Simple line plot")
|
|
||||||
// The size call could also be omitted, though the defaults don't consider window
|
|
||||||
// width, which is why we're not doing so here.
|
|
||||||
.size([content_width, 300.0])
|
|
||||||
.build(plot_ui, || {
|
|
||||||
// If this is called outside a plot build callback, the program will panic.
|
|
||||||
let x_positions = vec![0.1, 0.9];
|
|
||||||
let y_positions = vec![0.1, 0.9];
|
|
||||||
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_two_yaxis_plot(ui: &Ui, plot_ui: &PlotUi) {
|
impl LinePlotDemoState {
|
||||||
ui.text(im_str!(
|
/// Create a new line plots demo state object with default values in it.
|
||||||
"This header shows how to create a plot with multiple Y axes."
|
pub fn new() -> Self {
|
||||||
));
|
Self {
|
||||||
let content_width = ui.window_content_region_width();
|
linked_limits: Rc::new(RefCell::new(ImPlotRange { Min: 0.0, Max: 1.0 })),
|
||||||
Plot::new("Multiple Y axis plots")
|
}
|
||||||
// The size call could also be omitted, though the defaults don't consider window
|
}
|
||||||
// width, which is why we're not doing so here.
|
|
||||||
.size([content_width, 300.0])
|
|
||||||
.with_plot_flags(&(PlotFlags::NONE | PlotFlags::Y_AXIS_2))
|
|
||||||
.y_limits(
|
|
||||||
ImPlotRange { Min: 0.0, Max: 1.0 },
|
|
||||||
YAxisChoice::First,
|
|
||||||
Condition::Always,
|
|
||||||
)
|
|
||||||
.y_limits(
|
|
||||||
// One can also use [f32; 2], (f32, f32) and ImVec2 for limit setting
|
|
||||||
[1.0, 3.5],
|
|
||||||
YAxisChoice::Second,
|
|
||||||
Condition::Always,
|
|
||||||
)
|
|
||||||
.build(plot_ui, || {
|
|
||||||
let x_positions = vec![0.1, 0.9];
|
|
||||||
|
|
||||||
// The first Y axis is the default
|
pub fn show_basic_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
let y_positions = vec![0.1, 0.9];
|
ui.text(im_str!(
|
||||||
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
"This header just plots a line with as little code as possible."
|
||||||
|
));
|
||||||
|
let content_width = ui.window_content_region_width();
|
||||||
|
Plot::new("Simple line plot")
|
||||||
|
// The size call could also be omitted, though the defaults don't consider window
|
||||||
|
// width, which is why we're not doing so here.
|
||||||
|
.size([content_width, 300.0])
|
||||||
|
.build(plot_ui, || {
|
||||||
|
// If this is called outside a plot build callback, the program will panic.
|
||||||
|
let x_positions = vec![0.1, 0.9];
|
||||||
|
let y_positions = vec![0.1, 0.9];
|
||||||
|
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Now we switch to the second axis for the next call
|
pub fn show_two_yaxis_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
set_plot_y_axis(YAxisChoice::Second);
|
ui.text(im_str!(
|
||||||
let y_positions = vec![3.3, 1.2];
|
"This header shows how to create a plot with multiple Y axes."
|
||||||
PlotLine::new("legend label two").plot(&x_positions, &y_positions);
|
));
|
||||||
});
|
let content_width = ui.window_content_region_width();
|
||||||
}
|
Plot::new("Multiple Y axis plots")
|
||||||
|
// The size call could also be omitted, though the defaults don't consider window
|
||||||
|
// width, which is why we're not doing so here.
|
||||||
|
.size([content_width, 300.0])
|
||||||
|
.with_plot_flags(&(PlotFlags::NONE | PlotFlags::Y_AXIS_2))
|
||||||
|
.y_limits(
|
||||||
|
ImPlotRange { Min: 0.0, Max: 1.0 },
|
||||||
|
YAxisChoice::First,
|
||||||
|
Condition::Always,
|
||||||
|
)
|
||||||
|
.y_limits(
|
||||||
|
// One can also use [f32; 2], (f32, f32) and ImVec2 for limit setting
|
||||||
|
[1.0, 3.5],
|
||||||
|
YAxisChoice::Second,
|
||||||
|
Condition::Always,
|
||||||
|
)
|
||||||
|
.build(plot_ui, || {
|
||||||
|
let x_positions = vec![0.1, 0.9];
|
||||||
|
|
||||||
pub fn show_axis_equal_plot(ui: &Ui, plot_ui: &PlotUi) {
|
// The first Y axis is the default
|
||||||
ui.text(im_str!("This plot has axis equal set (1:1 aspect ratio)."));
|
let y_positions = vec![0.1, 0.9];
|
||||||
let content_width = ui.window_content_region_width();
|
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
||||||
Plot::new("Axis equal line plot")
|
|
||||||
// The size call could also be omitted, though the defaults don't consider window
|
|
||||||
// width, which is why we're not doing so here.
|
|
||||||
.size([content_width, 300.0])
|
|
||||||
.with_plot_flags(&(PlotFlags::NONE | PlotFlags::AXIS_EQUAL))
|
|
||||||
.build(plot_ui, || {
|
|
||||||
// If this is called outside a plot build callback, the program will panic.
|
|
||||||
let x_positions = vec![0.1, 0.9];
|
|
||||||
let y_positions = vec![0.1, 0.9];
|
|
||||||
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_configurable_plot(ui: &Ui, plot_ui: &PlotUi) {
|
// Now we switch to the second axis for the next call
|
||||||
ui.text(im_str!(
|
set_plot_y_axis(YAxisChoice::Second);
|
||||||
"This header demos what we can configure about plots."
|
let y_positions = vec![3.3, 1.2];
|
||||||
));
|
PlotLine::new("legend label two").plot(&x_positions, &y_positions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Settings for the plot
|
pub fn show_axis_equal_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
// - X and Y size in pixels
|
ui.text(im_str!("This plot has axis equal set (1:1 aspect ratio)."));
|
||||||
let x_size = 300.0;
|
let content_width = ui.window_content_region_width();
|
||||||
let y_size = 200.0;
|
Plot::new("Axis equal line plot")
|
||||||
// - Strings for the axis labels
|
// The size call could also be omitted, though the defaults don't consider window
|
||||||
let x_label = "X label!";
|
// width, which is why we're not doing so here.
|
||||||
let y_label = "Y label!";
|
.size([content_width, 300.0])
|
||||||
// - Plot limits
|
.with_plot_flags(&(PlotFlags::NONE | PlotFlags::AXIS_EQUAL))
|
||||||
let x_min = 2.0;
|
.build(plot_ui, || {
|
||||||
let x_max = 3.0;
|
// If this is called outside a plot build callback, the program will panic.
|
||||||
let y_min = 1.0;
|
let x_positions = vec![0.1, 0.9];
|
||||||
let y_max = 2.0;
|
let y_positions = vec![0.1, 0.9];
|
||||||
// - Plot flags, see the PlotFlags docs for more info
|
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
||||||
let plot_flags = PlotFlags::NONE;
|
});
|
||||||
// - Axis flags, see the AxisFlags docs for more info. All flags are bitflags-created,
|
}
|
||||||
// so they support a bunch of convenient operations, see https://docs.rs/bitflags
|
|
||||||
let x_axis_flags = AxisFlags::NONE;
|
|
||||||
let y_axis_flags = AxisFlags::NONE;
|
|
||||||
|
|
||||||
// - Unlabelled X axis ticks
|
pub fn show_configurable_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
let x_ticks = vec![2.2, 2.5, 2.8];
|
ui.text(im_str!(
|
||||||
|
"This header demos what we can configure about plots."
|
||||||
|
));
|
||||||
|
|
||||||
// - Labelled Y axis ticks
|
// Settings for the plot
|
||||||
let y_ticks = vec![(1.1, "A".to_owned()), (1.4, "B".to_owned())];
|
// - X and Y size in pixels
|
||||||
|
let x_size = 300.0;
|
||||||
|
let y_size = 200.0;
|
||||||
|
// - Strings for the axis labels
|
||||||
|
let x_label = "X label!";
|
||||||
|
let y_label = "Y label!";
|
||||||
|
// - Plot limits
|
||||||
|
let x_min = 2.0;
|
||||||
|
let x_max = 3.0;
|
||||||
|
let y_min = 1.0;
|
||||||
|
let y_max = 2.0;
|
||||||
|
// - Plot flags, see the PlotFlags docs for more info
|
||||||
|
let plot_flags = PlotFlags::NONE;
|
||||||
|
// - Axis flags, see the AxisFlags docs for more info. All flags are bitflags-created,
|
||||||
|
// so they support a bunch of convenient operations, see https://docs.rs/bitflags
|
||||||
|
let x_axis_flags = AxisFlags::NONE;
|
||||||
|
let y_axis_flags = AxisFlags::NONE;
|
||||||
|
|
||||||
// Axis labels
|
// - Unlabelled X axis ticks
|
||||||
Plot::new("Configured line plot")
|
let x_ticks = vec![2.2, 2.5, 2.8];
|
||||||
.size([x_size, y_size])
|
|
||||||
.x_label(&x_label)
|
|
||||||
.y_label(&y_label)
|
|
||||||
.x_limits(
|
|
||||||
ImPlotRange {
|
|
||||||
Min: x_min,
|
|
||||||
Max: x_max,
|
|
||||||
},
|
|
||||||
// Always means that the limits stay what we force them to here, even if the user
|
|
||||||
// scrolls or drags in the plot with the mouse. FirstUseEver sets the limits the
|
|
||||||
// first time the plot is drawn, but the user can then modify them and the change
|
|
||||||
// will stick.
|
|
||||||
Condition::Always,
|
|
||||||
)
|
|
||||||
.y_limits(
|
|
||||||
ImPlotRange {
|
|
||||||
Min: y_min,
|
|
||||||
Max: y_max,
|
|
||||||
},
|
|
||||||
YAxisChoice::First,
|
|
||||||
Condition::Always,
|
|
||||||
)
|
|
||||||
.x_ticks(&x_ticks, false)
|
|
||||||
.y_ticks_with_labels(YAxisChoice::First, &y_ticks, false)
|
|
||||||
// If any of these flag setting calls are omitted, the defaults are used.
|
|
||||||
.with_plot_flags(&plot_flags)
|
|
||||||
.with_x_axis_flags(&x_axis_flags)
|
|
||||||
.with_y_axis_flags(YAxisChoice::First, &y_axis_flags)
|
|
||||||
.with_legend_location(&PlotLocation::West, &PlotOrientation::Horizontal, true)
|
|
||||||
.build(plot_ui, || {
|
|
||||||
PlotLine::new("A line 2").plot(&vec![2.4, 2.9], &vec![1.1, 1.9]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_query_features_plot(ui: &Ui, plot_ui: &PlotUi) {
|
// - Labelled Y axis ticks
|
||||||
ui.text(im_str!(
|
let y_ticks = vec![(1.1, "A".to_owned()), (1.4, "B".to_owned())];
|
||||||
"This header demos how to use the querying features."
|
|
||||||
));
|
|
||||||
let content_width = ui.window_content_region_width();
|
|
||||||
|
|
||||||
// Create some containers for exfiltrating data from the closure below
|
// Axis labels
|
||||||
let mut hover_pos_plot: Option<ImPlotPoint> = None;
|
Plot::new("Configured line plot")
|
||||||
let mut hover_pos_pixels: Option<ImVec2> = None;
|
.size([x_size, y_size])
|
||||||
let mut hover_pos_from_pixels: Option<ImPlotPoint> = None;
|
.x_label(&x_label)
|
||||||
let mut plot_limits: Option<ImPlotLimits> = None;
|
.y_label(&y_label)
|
||||||
let mut query_limits: Option<ImPlotLimits> = None;
|
.x_limits(
|
||||||
let mut legend1_hovered = false;
|
ImPlotRange {
|
||||||
let mut legend2_hovered = false;
|
Min: x_min,
|
||||||
|
Max: x_max,
|
||||||
// Draw a plot
|
|
||||||
Plot::new("Plot querying")
|
|
||||||
.size([content_width, 300.0])
|
|
||||||
.x_limits(ImPlotRange { Min: 0.0, Max: 5.0 }, Condition::FirstUseEver)
|
|
||||||
.y_limits(
|
|
||||||
ImPlotRange { Min: 0.0, Max: 5.0 },
|
|
||||||
YAxisChoice::First,
|
|
||||||
Condition::FirstUseEver,
|
|
||||||
)
|
|
||||||
.with_plot_flags(&(PlotFlags::NONE | PlotFlags::QUERY))
|
|
||||||
.build(plot_ui, || {
|
|
||||||
if is_plot_hovered() {
|
|
||||||
hover_pos_plot = Some(get_plot_mouse_position(None));
|
|
||||||
hover_pos_pixels = Some(plot_to_pixels_vec2(&(hover_pos_plot.unwrap()), None));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting the plot position from pixels also works when the plot is not hovered,
|
|
||||||
// the coordinates are then simply outside the visible range.
|
|
||||||
hover_pos_from_pixels = Some(pixels_to_plot_vec2(
|
|
||||||
&ImVec2 {
|
|
||||||
x: ui.io().mouse_pos[0],
|
|
||||||
y: ui.io().mouse_pos[1],
|
|
||||||
},
|
},
|
||||||
None,
|
// Always means that the limits stay what we force them to here, even if the user
|
||||||
|
// scrolls or drags in the plot with the mouse. FirstUseEver sets the limits the
|
||||||
|
// first time the plot is drawn, but the user can then modify them and the change
|
||||||
|
// will stick.
|
||||||
|
Condition::Always,
|
||||||
|
)
|
||||||
|
.y_limits(
|
||||||
|
ImPlotRange {
|
||||||
|
Min: y_min,
|
||||||
|
Max: y_max,
|
||||||
|
},
|
||||||
|
YAxisChoice::First,
|
||||||
|
Condition::Always,
|
||||||
|
)
|
||||||
|
.x_ticks(&x_ticks, false)
|
||||||
|
.y_ticks_with_labels(YAxisChoice::First, &y_ticks, false)
|
||||||
|
// If any of these flag setting calls are omitted, the defaults are used.
|
||||||
|
.with_plot_flags(&plot_flags)
|
||||||
|
.with_x_axis_flags(&x_axis_flags)
|
||||||
|
.with_y_axis_flags(YAxisChoice::First, &y_axis_flags)
|
||||||
|
.with_legend_location(&PlotLocation::West, &PlotOrientation::Horizontal, true)
|
||||||
|
.build(plot_ui, || {
|
||||||
|
PlotLine::new("A line 2").plot(&vec![2.4, 2.9], &vec![1.1, 1.9]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_query_features_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
|
ui.text(im_str!(
|
||||||
|
"This header demos how to use the querying features."
|
||||||
|
));
|
||||||
|
let content_width = ui.window_content_region_width();
|
||||||
|
|
||||||
|
// Create some containers for exfiltrating data from the closure below
|
||||||
|
let mut hover_pos_plot: Option<ImPlotPoint> = None;
|
||||||
|
let mut hover_pos_pixels: Option<ImVec2> = None;
|
||||||
|
let mut hover_pos_from_pixels: Option<ImPlotPoint> = None;
|
||||||
|
let mut plot_limits: Option<ImPlotLimits> = None;
|
||||||
|
let mut query_limits: Option<ImPlotLimits> = None;
|
||||||
|
let mut legend1_hovered = false;
|
||||||
|
let mut legend2_hovered = false;
|
||||||
|
|
||||||
|
// Draw a plot
|
||||||
|
Plot::new("Plot querying")
|
||||||
|
.size([content_width, 300.0])
|
||||||
|
.x_limits(ImPlotRange { Min: 0.0, Max: 5.0 }, Condition::FirstUseEver)
|
||||||
|
.y_limits(
|
||||||
|
ImPlotRange { Min: 0.0, Max: 5.0 },
|
||||||
|
YAxisChoice::First,
|
||||||
|
Condition::FirstUseEver,
|
||||||
|
)
|
||||||
|
.with_plot_flags(&(PlotFlags::NONE | PlotFlags::QUERY))
|
||||||
|
.build(plot_ui, || {
|
||||||
|
if is_plot_hovered() {
|
||||||
|
hover_pos_plot = Some(get_plot_mouse_position(None));
|
||||||
|
hover_pos_pixels = Some(plot_to_pixels_vec2(&(hover_pos_plot.unwrap()), None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting the plot position from pixels also works when the plot is not hovered,
|
||||||
|
// the coordinates are then simply outside the visible range.
|
||||||
|
hover_pos_from_pixels = Some(pixels_to_plot_vec2(
|
||||||
|
&ImVec2 {
|
||||||
|
x: ui.io().mouse_pos[0],
|
||||||
|
y: ui.io().mouse_pos[1],
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Plot a line so we have a legend entry
|
||||||
|
PlotLine::new("Legend1").plot(&vec![2.0, 2.0], &vec![2.0, 1.0]);
|
||||||
|
PlotLine::new("Legend2").plot(&vec![0.0, 0.0], &vec![1.0, 1.0]);
|
||||||
|
legend1_hovered = is_legend_entry_hovered("Legend1");
|
||||||
|
legend2_hovered = is_legend_entry_hovered("Legend2");
|
||||||
|
|
||||||
|
if is_plot_queried() {
|
||||||
|
query_limits = Some(get_plot_query(None));
|
||||||
|
}
|
||||||
|
plot_limits = Some(get_plot_limits(None));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Print some previously-exfiltrated info. This is because calling
|
||||||
|
// things like is_plot_hovered or get_plot_mouse_position() outside
|
||||||
|
// of an actual Plot is not allowed.
|
||||||
|
if let Some(pos) = hover_pos_plot {
|
||||||
|
ui.text(im_str!("hovered at {}, {}", pos.x, pos.y));
|
||||||
|
}
|
||||||
|
if let Some(pixel_position) = hover_pos_pixels {
|
||||||
|
// Try out converting plot mouse position to pixel position
|
||||||
|
ui.text(im_str!(
|
||||||
|
"pixel pos from plot: {}, {}",
|
||||||
|
pixel_position.x,
|
||||||
|
pixel_position.y
|
||||||
));
|
));
|
||||||
|
ui.text(im_str!(
|
||||||
// Plot a line so we have a legend entry
|
"pixel pos from imgui: {}, {}",
|
||||||
PlotLine::new("Legend1").plot(&vec![2.0, 2.0], &vec![2.0, 1.0]);
|
ui.io().mouse_pos[0],
|
||||||
PlotLine::new("Legend2").plot(&vec![0.0, 0.0], &vec![1.0, 1.0]);
|
ui.io().mouse_pos[1]
|
||||||
legend1_hovered = is_legend_entry_hovered("Legend1");
|
));
|
||||||
legend2_hovered = is_legend_entry_hovered("Legend2");
|
}
|
||||||
|
if let Some(limits) = plot_limits {
|
||||||
if is_plot_queried() {
|
ui.text(im_str!("Plot limits are {:#?}", limits));
|
||||||
query_limits = Some(get_plot_query(None));
|
}
|
||||||
}
|
if let Some(query) = query_limits {
|
||||||
plot_limits = Some(get_plot_limits(None));
|
ui.text(im_str!("Query limits are {:#?}", query));
|
||||||
});
|
}
|
||||||
|
|
||||||
// Print some previously-exfiltrated info. This is because calling
|
|
||||||
// things like is_plot_hovered or get_plot_mouse_position() outside
|
|
||||||
// of an actual Plot is not allowed.
|
|
||||||
if let Some(pos) = hover_pos_plot {
|
|
||||||
ui.text(im_str!("hovered at {}, {}", pos.x, pos.y));
|
|
||||||
}
|
|
||||||
if let Some(pixel_position) = hover_pos_pixels {
|
|
||||||
// Try out converting plot mouse position to pixel position
|
|
||||||
ui.text(im_str!(
|
ui.text(im_str!(
|
||||||
"pixel pos from plot: {}, {}",
|
"Legend hovering - 1: {}, 2: {}",
|
||||||
pixel_position.x,
|
legend1_hovered,
|
||||||
pixel_position.y
|
legend2_hovered
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Try out converting pixel position to plot position
|
||||||
|
if let Some(pos) = hover_pos_from_pixels {
|
||||||
|
ui.text(im_str!("plot pos from imgui: {}, {}", pos.x, pos.y,));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_style_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
ui.text(im_str!(
|
ui.text(im_str!(
|
||||||
"pixel pos from imgui: {}, {}",
|
"This header demos how to use the styling features."
|
||||||
ui.io().mouse_pos[0],
|
|
||||||
ui.io().mouse_pos[1]
|
|
||||||
));
|
));
|
||||||
}
|
let content_width = ui.window_content_region_width();
|
||||||
if let Some(limits) = plot_limits {
|
|
||||||
ui.text(im_str!("Plot limits are {:#?}", limits));
|
|
||||||
}
|
|
||||||
if let Some(query) = query_limits {
|
|
||||||
ui.text(im_str!("Query limits are {:#?}", query));
|
|
||||||
}
|
|
||||||
ui.text(im_str!(
|
|
||||||
"Legend hovering - 1: {}, 2: {}",
|
|
||||||
legend1_hovered,
|
|
||||||
legend2_hovered
|
|
||||||
));
|
|
||||||
|
|
||||||
// Try out converting pixel position to plot position
|
// The style stack works the same as for other imgui things - we can push
|
||||||
if let Some(pos) = hover_pos_from_pixels {
|
// things to have them apply, then pop again to undo the change. In implot-rs,
|
||||||
ui.text(im_str!("plot pos from imgui: {}, {}", pos.x, pos.y,));
|
// pushing returns a value on which we have to call .pop() later. Pushing
|
||||||
|
// variables can be done outside of plot calls as well.
|
||||||
|
let style = push_style_color(&PlotColorElement::PlotBg, 1.0, 1.0, 1.0, 0.2);
|
||||||
|
Plot::new("Style demo plot")
|
||||||
|
.size([content_width, 300.0])
|
||||||
|
.x_limits(ImPlotRange { Min: 0.0, Max: 6.0 }, Condition::Always)
|
||||||
|
.y_limits(
|
||||||
|
ImPlotRange {
|
||||||
|
Min: -1.0,
|
||||||
|
Max: 3.0,
|
||||||
|
},
|
||||||
|
YAxisChoice::First,
|
||||||
|
Condition::Always,
|
||||||
|
)
|
||||||
|
.with_plot_flags(&(PlotFlags::NONE))
|
||||||
|
.with_y_axis_flags(YAxisChoice::First, &(AxisFlags::NONE))
|
||||||
|
.build(plot_ui, || {
|
||||||
|
// Markers can be selected as shown here. The markers are internally represented
|
||||||
|
// as an u32, hence this calling style.
|
||||||
|
let markerchoice = push_style_var_i32(&StyleVar::Marker, Marker::Cross as i32);
|
||||||
|
PlotLine::new("Left eye").plot(&vec![2.0, 2.0], &vec![2.0, 1.0]);
|
||||||
|
// Calling pop() on the return value of the push above will undo the marker choice.
|
||||||
|
markerchoice.pop();
|
||||||
|
|
||||||
|
// Line weights can be set the same way, along with some other things - see
|
||||||
|
// the docs of StyleVar for more info.
|
||||||
|
let lineweight = push_style_var_f32(&StyleVar::LineWeight, 5.0);
|
||||||
|
PlotLine::new("Right eye").plot(&vec![4.0, 4.0], &vec![2.0, 1.0]);
|
||||||
|
lineweight.pop();
|
||||||
|
|
||||||
|
let x_values = vec![1.0, 2.0, 4.0, 5.0];
|
||||||
|
let y_values = vec![1.0, 0.0, 0.0, 1.0];
|
||||||
|
PlotLine::new("Mouth").plot(&x_values, &y_values);
|
||||||
|
});
|
||||||
|
|
||||||
|
style.pop();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_style_plot(ui: &Ui, plot_ui: &PlotUi) {
|
pub fn show_colormaps_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
ui.text(im_str!(
|
ui.text(im_str!("This header demos how to select colormaps."));
|
||||||
"This header demos how to use the styling features."
|
let content_width = ui.window_content_region_width();
|
||||||
));
|
|
||||||
let content_width = ui.window_content_region_width();
|
|
||||||
|
|
||||||
// The style stack works the same as for other imgui things - we can push
|
// Select a colormap from the presets. The presets are listed in the Colormap enum
|
||||||
// things to have them apply, then pop again to undo the change. In implot-rs,
|
// and usually have something from 9 to 11 colors in them, with the second number
|
||||||
// pushing returns a value on which we have to call .pop() later. Pushing
|
// being the option to resample the colormap to a custom number of colors if picked
|
||||||
// variables can be done outside of plot calls as well.
|
// higher than 1.
|
||||||
let style = push_style_color(&PlotColorElement::PlotBg, 1.0, 1.0, 1.0, 0.2);
|
set_colormap_from_preset(Colormap::Plasma, 1);
|
||||||
Plot::new("Style demo plot")
|
|
||||||
.size([content_width, 300.0])
|
Plot::new("Colormap demo plot")
|
||||||
.x_limits(ImPlotRange { Min: 0.0, Max: 6.0 }, Condition::Always)
|
.size([content_width, 300.0])
|
||||||
.y_limits(
|
.build(plot_ui, || {
|
||||||
ImPlotRange {
|
(1..10)
|
||||||
Min: -1.0,
|
.map(|x| x as f64 * 0.1)
|
||||||
Max: 3.0,
|
.map(|x| {
|
||||||
|
PlotLine::new(&format!("{:3.3}", x)).plot(&vec![0.1, 0.9], &vec![x, x])
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
});
|
||||||
|
|
||||||
|
// One can also specify a colormap as a vector of RGBA colors. ImPlot uses ImVec4 for this,
|
||||||
|
// so we follow suit. Make sure to set the last number (w in ImVec4) to 1.0 to see anything -
|
||||||
|
// it's the alpha channel.
|
||||||
|
set_colormap_from_vec(vec![
|
||||||
|
ImVec4 {
|
||||||
|
x: 0.9,
|
||||||
|
y: 0.9,
|
||||||
|
z: 0.0,
|
||||||
|
w: 1.0,
|
||||||
},
|
},
|
||||||
YAxisChoice::First,
|
ImVec4 {
|
||||||
Condition::Always,
|
x: 0.0,
|
||||||
)
|
y: 0.9,
|
||||||
.with_plot_flags(&(PlotFlags::NONE))
|
z: 0.9,
|
||||||
.with_y_axis_flags(YAxisChoice::First, &(AxisFlags::NONE))
|
w: 1.0,
|
||||||
.build(plot_ui, || {
|
},
|
||||||
// Markers can be selected as shown here. The markers are internally represented
|
]);
|
||||||
// as an u32, hence this calling style.
|
|
||||||
let markerchoice = push_style_var_i32(&StyleVar::Marker, Marker::Cross as i32);
|
|
||||||
PlotLine::new("Left eye").plot(&vec![2.0, 2.0], &vec![2.0, 1.0]);
|
|
||||||
// Calling pop() on the return value of the push above will undo the marker choice.
|
|
||||||
markerchoice.pop();
|
|
||||||
|
|
||||||
// Line weights can be set the same way, along with some other things - see
|
Plot::new("Colormap demo plot #2")
|
||||||
// the docs of StyleVar for more info.
|
.size([content_width, 300.0])
|
||||||
let lineweight = push_style_var_f32(&StyleVar::LineWeight, 5.0);
|
.build(plot_ui, || {
|
||||||
PlotLine::new("Right eye").plot(&vec![4.0, 4.0], &vec![2.0, 1.0]);
|
(1..10)
|
||||||
lineweight.pop();
|
.map(|x| x as f64 * 0.1)
|
||||||
|
.map(|x| {
|
||||||
|
PlotLine::new(&format!("{:3.3}", x)).plot(&vec![0.1, 0.9], &vec![x, x])
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
});
|
||||||
|
|
||||||
let x_values = vec![1.0, 2.0, 4.0, 5.0];
|
// Colormaps are not pushed, they are simply set, because they don't stack or anything.
|
||||||
let y_values = vec![1.0, 0.0, 0.0, 1.0];
|
// We can reset to the default by just setting the "Standard" preset.
|
||||||
PlotLine::new("Mouth").plot(&x_values, &y_values);
|
set_colormap_from_preset(Colormap::Standard, 0);
|
||||||
});
|
|
||||||
|
|
||||||
style.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_colormaps_plot(ui: &Ui, plot_ui: &PlotUi) {
|
|
||||||
ui.text(im_str!("This header demos how to select colormaps."));
|
|
||||||
let content_width = ui.window_content_region_width();
|
|
||||||
|
|
||||||
// Select a colormap from the presets. The presets are listed in the Colormap enum
|
|
||||||
// and usually have something from 9 to 11 colors in them, with the second number
|
|
||||||
// being the option to resample the colormap to a custom number of colors if picked
|
|
||||||
// higher than 1.
|
|
||||||
set_colormap_from_preset(Colormap::Plasma, 1);
|
|
||||||
|
|
||||||
Plot::new("Colormap demo plot")
|
|
||||||
.size([content_width, 300.0])
|
|
||||||
.build(plot_ui, || {
|
|
||||||
(1..10)
|
|
||||||
.map(|x| x as f64 * 0.1)
|
|
||||||
.map(|x| PlotLine::new(&format!("{:3.3}", x)).plot(&vec![0.1, 0.9], &vec![x, x]))
|
|
||||||
.count();
|
|
||||||
});
|
|
||||||
|
|
||||||
// One can also specify a colormap as a vector of RGBA colors. ImPlot uses ImVec4 for this,
|
|
||||||
// so we follow suit. Make sure to set the last number (w in ImVec4) to 1.0 to see anything -
|
|
||||||
// it's the alpha channel.
|
|
||||||
set_colormap_from_vec(vec![
|
|
||||||
ImVec4 {
|
|
||||||
x: 0.9,
|
|
||||||
y: 0.9,
|
|
||||||
z: 0.0,
|
|
||||||
w: 1.0,
|
|
||||||
},
|
|
||||||
ImVec4 {
|
|
||||||
x: 0.0,
|
|
||||||
y: 0.9,
|
|
||||||
z: 0.9,
|
|
||||||
w: 1.0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
Plot::new("Colormap demo plot #2")
|
|
||||||
.size([content_width, 300.0])
|
|
||||||
.build(plot_ui, || {
|
|
||||||
(1..10)
|
|
||||||
.map(|x| x as f64 * 0.1)
|
|
||||||
.map(|x| PlotLine::new(&format!("{:3.3}", x)).plot(&vec![0.1, 0.9], &vec![x, x]))
|
|
||||||
.count();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Colormaps are not pushed, they are simply set, because they don't stack or anything.
|
|
||||||
// We can reset to the default by just setting the "Standard" preset.
|
|
||||||
set_colormap_from_preset(Colormap::Standard, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_conversions_plot(ui: &Ui, plot_ui: &PlotUi) {
|
|
||||||
ui.text(im_str!(
|
|
||||||
"This header demonstrates (in code) how to convert various ranges into ImRange"
|
|
||||||
));
|
|
||||||
let content_width = ui.window_content_region_width();
|
|
||||||
Plot::new("Simple line plot, conversion 1")
|
|
||||||
.size([content_width, 300.0])
|
|
||||||
.x_limits(ImVec2 { x: 0.0, y: 1.0 }, Condition::Always)
|
|
||||||
.y_limits([0.0, 1.0], YAxisChoice::First, Condition::Always)
|
|
||||||
.build(plot_ui, || {
|
|
||||||
// If this is called outside a plot build callback, the program will panic.
|
|
||||||
let x_positions = vec![0.1, 0.9];
|
|
||||||
let y_positions = vec![0.1, 0.9];
|
|
||||||
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_demo_headers(ui: &Ui, plot_ui: &PlotUi) {
|
|
||||||
if CollapsingHeader::new(im_str!("Line plot: Basic")).build(&ui) {
|
|
||||||
show_basic_plot(&ui, &plot_ui);
|
|
||||||
}
|
}
|
||||||
if CollapsingHeader::new(im_str!("Line plot: Configured")).build(&ui) {
|
|
||||||
show_configurable_plot(&ui, &plot_ui);
|
pub fn show_conversions_plot(ui: &Ui, plot_ui: &PlotUi) {
|
||||||
|
ui.text(im_str!(
|
||||||
|
"This header demonstrates (in code) how to convert various ranges into ImRange"
|
||||||
|
));
|
||||||
|
let content_width = ui.window_content_region_width();
|
||||||
|
Plot::new("Simple line plot, conversion 1")
|
||||||
|
.size([content_width, 300.0])
|
||||||
|
.x_limits(ImVec2 { x: 0.0, y: 1.0 }, Condition::Always)
|
||||||
|
.y_limits([0.0, 1.0], YAxisChoice::First, Condition::Always)
|
||||||
|
.build(plot_ui, || {
|
||||||
|
// If this is called outside a plot build callback, the program will panic.
|
||||||
|
let x_positions = vec![0.1, 0.9];
|
||||||
|
let y_positions = vec![0.1, 0.9];
|
||||||
|
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if CollapsingHeader::new(im_str!("Line Plot: Plot queries")).build(&ui) {
|
|
||||||
show_query_features_plot(&ui, &plot_ui);
|
pub fn show_linked_x_axis_plots(&mut self, ui: &Ui, plot_ui: &PlotUi) {
|
||||||
|
ui.text(im_str!(
|
||||||
|
"These plots have their X axes linked, but not the Y axes"
|
||||||
|
));
|
||||||
|
let content_width = ui.window_content_region_width();
|
||||||
|
Plot::new("Linked plot 1")
|
||||||
|
.size([content_width, 300.0])
|
||||||
|
.linked_x_limits(self.linked_limits.clone())
|
||||||
|
.build(plot_ui, || {
|
||||||
|
let x_positions = vec![0.1, 0.9];
|
||||||
|
let y_positions = vec![0.1, 0.9];
|
||||||
|
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
||||||
|
});
|
||||||
|
Plot::new("Linked plot 2")
|
||||||
|
.size([content_width, 300.0])
|
||||||
|
.linked_x_limits(self.linked_limits.clone())
|
||||||
|
.build(plot_ui, || {
|
||||||
|
let x_positions = vec![0.1, 0.9];
|
||||||
|
let y_positions = vec![0.1, 0.9];
|
||||||
|
PlotLine::new("legend label").plot(&x_positions, &y_positions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if CollapsingHeader::new(im_str!("Line plot: Plot styling")).build(&ui) {
|
|
||||||
show_style_plot(&ui, &plot_ui);
|
pub fn show_demo_headers(&mut self, ui: &Ui, plot_ui: &PlotUi) {
|
||||||
}
|
if CollapsingHeader::new(im_str!("Line plot: Basic")).build(&ui) {
|
||||||
if CollapsingHeader::new(im_str!("Line plot: Colormaps")).build(&ui) {
|
Self::show_basic_plot(&ui, &plot_ui);
|
||||||
show_colormaps_plot(&ui, &plot_ui);
|
}
|
||||||
}
|
if CollapsingHeader::new(im_str!("Line plot: Configured")).build(&ui) {
|
||||||
if CollapsingHeader::new(im_str!("Line plot: Multiple Y Axes")).build(&ui) {
|
Self::show_configurable_plot(&ui, &plot_ui);
|
||||||
show_two_yaxis_plot(&ui, &plot_ui);
|
}
|
||||||
}
|
if CollapsingHeader::new(im_str!("Line Plot: Plot queries")).build(&ui) {
|
||||||
if CollapsingHeader::new(im_str!("Line plot: \"Axis equal\"")).build(&ui) {
|
Self::show_query_features_plot(&ui, &plot_ui);
|
||||||
show_axis_equal_plot(&ui, &plot_ui);
|
}
|
||||||
}
|
if CollapsingHeader::new(im_str!("Line plot: Plot styling")).build(&ui) {
|
||||||
if CollapsingHeader::new(im_str!("Line plot: Range conversions")).build(&ui) {
|
Self::show_style_plot(&ui, &plot_ui);
|
||||||
show_conversions_plot(&ui, &plot_ui);
|
}
|
||||||
|
if CollapsingHeader::new(im_str!("Line plot: Colormaps")).build(&ui) {
|
||||||
|
Self::show_colormaps_plot(&ui, &plot_ui);
|
||||||
|
}
|
||||||
|
if CollapsingHeader::new(im_str!("Line plot: Multiple Y Axes")).build(&ui) {
|
||||||
|
Self::show_two_yaxis_plot(&ui, &plot_ui);
|
||||||
|
}
|
||||||
|
if CollapsingHeader::new(im_str!("Line plot: \"Axis equal\"")).build(&ui) {
|
||||||
|
Self::show_axis_equal_plot(&ui, &plot_ui);
|
||||||
|
}
|
||||||
|
if CollapsingHeader::new(im_str!("Line plot: Range conversions")).build(&ui) {
|
||||||
|
Self::show_conversions_plot(&ui, &plot_ui);
|
||||||
|
}
|
||||||
|
if CollapsingHeader::new(im_str!("Line plot: Linked plots")).build(&ui) {
|
||||||
|
self.show_linked_x_axis_plots(&ui, &plot_ui);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ fn main() {
|
||||||
let system = support::init(file!());
|
let system = support::init(file!());
|
||||||
let mut showing_demo = false;
|
let mut showing_demo = false;
|
||||||
let mut showing_rust_demo = true;
|
let mut showing_rust_demo = true;
|
||||||
|
let mut demo_state = examples_shared::DemoState::new();
|
||||||
let plotcontext = Context::create();
|
let plotcontext = Context::create();
|
||||||
system.main_loop(move |_, ui| {
|
system.main_loop(move |_, ui| {
|
||||||
// The context is moved into the closure after creation so plot_ui is valid.
|
// The context is moved into the closure after creation so plot_ui is valid.
|
||||||
|
@ -18,7 +19,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if showing_rust_demo {
|
if showing_rust_demo {
|
||||||
examples_shared::show_demos(ui, &plot_ui);
|
demo_state.show_demos(ui, &plot_ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::new(im_str!("Welcome to the ImPlot-rs demo!"))
|
Window::new(im_str!("Welcome to the ImPlot-rs demo!"))
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use imgui::{im_str, Condition, Window};
|
use imgui::{im_str, Condition, Window};
|
||||||
use implot::Context;
|
use implot::Context;
|
||||||
|
|
||||||
|
@ -9,6 +8,7 @@ fn main() {
|
||||||
let system = support::init(file!());
|
let system = support::init(file!());
|
||||||
let mut showing_demo = false;
|
let mut showing_demo = false;
|
||||||
let mut showing_rust_demo = true;
|
let mut showing_rust_demo = true;
|
||||||
|
let mut demo_state = examples_shared::DemoState::new();
|
||||||
let plotcontext = Context::create();
|
let plotcontext = Context::create();
|
||||||
system.main_loop(move |_, ui| {
|
system.main_loop(move |_, ui| {
|
||||||
// The context is moved into the closure after creation so plot_ui is valid.
|
// The context is moved into the closure after creation so plot_ui is valid.
|
||||||
|
@ -19,7 +19,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if showing_rust_demo {
|
if showing_rust_demo {
|
||||||
examples_shared::show_demos(ui, &plot_ui);
|
demo_state.show_demos(ui, &plot_ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::new(im_str!("Welcome to the ImPlot-rs demo!"))
|
Window::new(im_str!("Welcome to the ImPlot-rs demo!"))
|
||||||
|
|
160
src/plot.rs
160
src/plot.rs
|
@ -7,6 +7,7 @@ use bitflags::bitflags;
|
||||||
pub use imgui::Condition;
|
pub use imgui::Condition;
|
||||||
use imgui::{im_str, ImString};
|
use imgui::{im_str, ImString};
|
||||||
use implot_sys as sys;
|
use implot_sys as sys;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
pub use sys::{ImPlotLimits, ImPlotPoint, ImPlotRange, ImVec2, ImVec4};
|
pub use sys::{ImPlotLimits, ImPlotPoint, ImPlotRange, ImVec2, ImVec4};
|
||||||
|
|
||||||
const DEFAULT_PLOT_SIZE_X: f32 = 400.0;
|
const DEFAULT_PLOT_SIZE_X: f32 = 400.0;
|
||||||
|
@ -77,6 +78,15 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internally-used struct for storing axis limits
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum AxisLimitSpecification {
|
||||||
|
/// Direct limits, specified as values
|
||||||
|
Single(ImPlotRange, Condition),
|
||||||
|
/// Limits that are linked to limits of other plots (via clones of the same Rc)
|
||||||
|
Linked(Rc<RefCell<ImPlotRange>>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Struct to represent an ImPlot. This is the main construct used to contain all kinds of plots in ImPlot.
|
/// Struct to represent an ImPlot. This is the main construct used to contain all kinds of plots in ImPlot.
|
||||||
///
|
///
|
||||||
/// `Plot` is to be used (within an imgui window) with the following pattern:
|
/// `Plot` is to be used (within an imgui window) with the following pattern:
|
||||||
|
@ -106,13 +116,9 @@ pub struct Plot {
|
||||||
/// afterwards, and this ensures the ImString itself will stay alive long enough for the plot.
|
/// afterwards, and this ensures the ImString itself will stay alive long enough for the plot.
|
||||||
y_label: ImString,
|
y_label: ImString,
|
||||||
/// X axis limits, if present
|
/// X axis limits, if present
|
||||||
x_limits: Option<ImPlotRange>,
|
x_limits: Option<AxisLimitSpecification>,
|
||||||
/// Y axis limits, if present
|
/// Y axis limits, if present
|
||||||
y_limits: [Option<ImPlotRange>; NUMBER_OF_Y_AXES],
|
y_limits: [Option<AxisLimitSpecification>; NUMBER_OF_Y_AXES],
|
||||||
/// Condition on which the x limits are set
|
|
||||||
x_limit_condition: Option<Condition>,
|
|
||||||
/// Condition on which the y limits are set for each of the axes
|
|
||||||
y_limit_condition: [Option<Condition>; NUMBER_OF_Y_AXES],
|
|
||||||
/// Positions for custom X axis ticks, if any
|
/// Positions for custom X axis ticks, if any
|
||||||
x_tick_positions: Option<Vec<f64>>,
|
x_tick_positions: Option<Vec<f64>>,
|
||||||
/// Labels for custom X axis ticks, if any. I'd prefer to store these together
|
/// Labels for custom X axis ticks, if any. I'd prefer to store these together
|
||||||
|
@ -164,9 +170,7 @@ impl Plot {
|
||||||
x_label: im_str!("").into(),
|
x_label: im_str!("").into(),
|
||||||
y_label: im_str!("").into(),
|
y_label: im_str!("").into(),
|
||||||
x_limits: None,
|
x_limits: None,
|
||||||
y_limits: [None; NUMBER_OF_Y_AXES],
|
y_limits: Default::default(),
|
||||||
x_limit_condition: None,
|
|
||||||
y_limit_condition: [None; NUMBER_OF_Y_AXES],
|
|
||||||
x_tick_positions: None,
|
x_tick_positions: None,
|
||||||
x_tick_labels: None,
|
x_tick_labels: None,
|
||||||
show_x_default_ticks: false,
|
show_x_default_ticks: false,
|
||||||
|
@ -202,17 +206,33 @@ impl Plot {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the x limits of the plot
|
/// Set the x limits of the plot.
|
||||||
|
///
|
||||||
|
/// Note: This conflicts with `linked_x_limits`, whichever is called last on plot construction
|
||||||
|
/// takes effect.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn x_limits<L: Into<ImPlotRange>>(mut self, limits: L, condition: Condition) -> Self {
|
pub fn x_limits<L: Into<ImPlotRange>>(mut self, limits: L, condition: Condition) -> Self {
|
||||||
self.x_limits = Some(limits.into());
|
self.x_limits = Some(AxisLimitSpecification::Single(limits.into(), condition));
|
||||||
self.x_limit_condition = Some(condition);
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set linked x limits for this plot. Pass clones of the same `Rc` into other plots
|
||||||
|
/// to link their limits with the same values.
|
||||||
|
///
|
||||||
|
/// Note: This conflicts with `x_limits`, whichever is called last on plot construction takes
|
||||||
|
/// effect.
|
||||||
|
#[inline]
|
||||||
|
pub fn linked_x_limits(mut self, limits: Rc<RefCell<ImPlotRange>>) -> Self {
|
||||||
|
self.x_limits = Some(AxisLimitSpecification::Linked(limits));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the Y limits of the plot for the given Y axis. Call multiple times with different
|
/// Set the Y limits of the plot for the given Y axis. Call multiple times with different
|
||||||
/// `y_axis_choice` values to set for multiple axes, or use the convenience methods such as
|
/// `y_axis_choice` values to set for multiple axes, or use the convenience methods such as
|
||||||
/// [`Plot::y1_limits`].
|
/// [`Plot::y1_limits`].
|
||||||
|
///
|
||||||
|
/// Note: This conflicts with `linked_y_limits`, whichever is called last on plot construction
|
||||||
|
/// takes effect for a given axis.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn y_limits<L: Into<ImPlotRange>>(
|
pub fn y_limits<L: Into<ImPlotRange>>(
|
||||||
mut self,
|
mut self,
|
||||||
|
@ -221,32 +241,73 @@ impl Plot {
|
||||||
condition: Condition,
|
condition: Condition,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let axis_index = y_axis_choice as usize;
|
let axis_index = y_axis_choice as usize;
|
||||||
self.y_limits[axis_index] = Some(limits.into());
|
self.y_limits[axis_index] = Some(AxisLimitSpecification::Single(limits.into(), condition));
|
||||||
self.y_limit_condition[axis_index] = Some(condition);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to directly set the Y limits for the first Y axis. To programmatically
|
/// Convenience function to directly set the Y limits for the first Y axis. To programmatically
|
||||||
/// (or on demand) decide which axie to set limits for, use [`Plot::y_limits`]
|
/// (or on demand) decide which axis to set limits for, use [`Plot::y_limits`]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn y1_limits<L: Into<ImPlotRange>>(self, limits: L, condition: Condition) -> Self {
|
pub fn y1_limits<L: Into<ImPlotRange>>(self, limits: L, condition: Condition) -> Self {
|
||||||
self.y_limits(limits, YAxisChoice::First, condition)
|
self.y_limits(limits, YAxisChoice::First, condition)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to directly set the Y limits for the second Y axis. To
|
/// Convenience function to directly set the Y limits for the second Y axis. To
|
||||||
/// programmatically (or on demand) decide which axie to set limits for, use [`Plot::y_limits`]
|
/// programmatically (or on demand) decide which axis to set limits for, use [`Plot::y_limits`]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn y2_limits<L: Into<ImPlotRange>>(self, limits: L, condition: Condition) -> Self {
|
pub fn y2_limits<L: Into<ImPlotRange>>(self, limits: L, condition: Condition) -> Self {
|
||||||
self.y_limits(limits, YAxisChoice::Second, condition)
|
self.y_limits(limits, YAxisChoice::Second, condition)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to directly set the Y limits for the third Y axis. To programmatically
|
/// Convenience function to directly set the Y limits for the third Y axis. To programmatically
|
||||||
/// (or on demand) decide which axie to set limits for, use [`Plot::y_limits`]
|
/// (or on demand) decide which axis to set limits for, use [`Plot::y_limits`]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn y3_limits<L: Into<ImPlotRange>>(self, limits: L, condition: Condition) -> Self {
|
pub fn y3_limits<L: Into<ImPlotRange>>(self, limits: L, condition: Condition) -> Self {
|
||||||
self.y_limits(limits, YAxisChoice::Third, condition)
|
self.y_limits(limits, YAxisChoice::Third, condition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set linked Y limits of the plot for the given Y axis. Pass clones of the same `Rc` into
|
||||||
|
/// other plots to link their limits with the same values. Call multiple times with different
|
||||||
|
/// `y_axis_choice` values to set for multiple axes, or use the convenience methods such as
|
||||||
|
/// [`Plot::y1_limits`].
|
||||||
|
///
|
||||||
|
/// Note: This conflicts with `y_limits`, whichever is called last on plot construction takes
|
||||||
|
/// effect for a given axis.
|
||||||
|
#[inline]
|
||||||
|
pub fn linked_y_limits(
|
||||||
|
mut self,
|
||||||
|
limits: Rc<RefCell<ImPlotRange>>,
|
||||||
|
y_axis_choice: YAxisChoice,
|
||||||
|
) -> Self {
|
||||||
|
let axis_index = y_axis_choice as usize;
|
||||||
|
self.y_limits[axis_index] = Some(AxisLimitSpecification::Linked(limits));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function to directly set linked Y limits for the first Y axis. To
|
||||||
|
/// programmatically (or on demand) decide which axis to set limits for, use
|
||||||
|
/// [`Plot::linked_y_limits`].
|
||||||
|
#[inline]
|
||||||
|
pub fn linked_y1_limits(self, limits: Rc<RefCell<ImPlotRange>>) -> Self {
|
||||||
|
self.linked_y_limits(limits, YAxisChoice::First)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function to directly set linked Y limits for the second Y axis. To
|
||||||
|
/// programmatically (or on demand) decide which axis to set limits for, use
|
||||||
|
/// [`Plot::linked_y_limits`].
|
||||||
|
#[inline]
|
||||||
|
pub fn linked_y2_limits(self, limits: Rc<RefCell<ImPlotRange>>) -> Self {
|
||||||
|
self.linked_y_limits(limits, YAxisChoice::Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function to directly set linked Y limits for the third Y axis. To
|
||||||
|
/// programmatically (or on demand) decide which axis to set limits for, use
|
||||||
|
/// [`Plot::linked_y_limits`].
|
||||||
|
#[inline]
|
||||||
|
pub fn linked_y3_limits(self, limits: Rc<RefCell<ImPlotRange>>) -> Self {
|
||||||
|
self.linked_y_limits(limits, YAxisChoice::Third)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set X ticks without labels for the plot. The vector contains one label each in
|
/// Set X ticks without labels for the plot. The vector contains one label each in
|
||||||
/// the form of a tuple `(label_position, label_string)`. The `show_default` setting
|
/// the form of a tuple `(label_position, label_string)`. The `show_default` setting
|
||||||
/// determines whether the default ticks are also shown.
|
/// determines whether the default ticks are also shown.
|
||||||
|
@ -343,20 +404,27 @@ impl Plot {
|
||||||
|
|
||||||
/// Internal helper function to set axis limits in case they are specified.
|
/// Internal helper function to set axis limits in case they are specified.
|
||||||
fn maybe_set_axis_limits(&self) {
|
fn maybe_set_axis_limits(&self) {
|
||||||
// Set X limits if specified
|
// Limit-setting can either happen via direct limits or through linked limits. The version
|
||||||
if let (Some(limits), Some(condition)) = (self.x_limits, self.x_limit_condition) {
|
// of implot we link to here has different APIs for the two (separate per-axis calls for
|
||||||
|
// direct, and one call for everything together for linked), hence the code here is a bit
|
||||||
|
// clunky and takes the two approaches separately instead of a unified "match".
|
||||||
|
|
||||||
|
// --- Direct limit-setting ---
|
||||||
|
if let Some(AxisLimitSpecification::Single(limits, condition)) = &self.x_limits {
|
||||||
unsafe {
|
unsafe {
|
||||||
sys::ImPlot_SetNextPlotLimitsX(limits.Min, limits.Max, condition as sys::ImGuiCond);
|
sys::ImPlot_SetNextPlotLimitsX(
|
||||||
|
limits.Min,
|
||||||
|
limits.Max,
|
||||||
|
*condition as sys::ImGuiCond,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Y limits if specified
|
|
||||||
self.y_limits
|
self.y_limits
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.y_limit_condition.iter())
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(k, (limits, condition))| {
|
.for_each(|(k, limit_spec)| {
|
||||||
if let (Some(limits), Some(condition)) = (limits, condition) {
|
if let Some(AxisLimitSpecification::Single(limits, condition)) = limit_spec {
|
||||||
unsafe {
|
unsafe {
|
||||||
sys::ImPlot_SetNextPlotLimitsY(
|
sys::ImPlot_SetNextPlotLimitsY(
|
||||||
limits.Min,
|
limits.Min,
|
||||||
|
@ -367,6 +435,50 @@ impl Plot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Linked limit-setting ---
|
||||||
|
let (xmin_pointer, xmax_pointer) =
|
||||||
|
if let Some(AxisLimitSpecification::Linked(value)) = &self.x_limits {
|
||||||
|
let mut borrowed = value.borrow_mut();
|
||||||
|
(
|
||||||
|
&mut (*borrowed).Min as *mut _,
|
||||||
|
&mut (*borrowed).Max as *mut _,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(std::ptr::null_mut(), std::ptr::null_mut())
|
||||||
|
};
|
||||||
|
|
||||||
|
let y_limit_pointers: Vec<(*mut f64, *mut f64)> = self
|
||||||
|
.y_limits
|
||||||
|
.iter()
|
||||||
|
.map(|limit_spec| {
|
||||||
|
if let Some(AxisLimitSpecification::Linked(value)) = limit_spec {
|
||||||
|
let mut borrowed = value.borrow_mut();
|
||||||
|
(
|
||||||
|
&mut (*borrowed).Min as *mut _,
|
||||||
|
&mut (*borrowed).Max as *mut _,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(std::ptr::null_mut(), std::ptr::null_mut())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Calling this unconditionally here as calling it with all NULL pointers should not
|
||||||
|
// affect anything. In terms of unsafety, the pointers should be OK as long as any plot
|
||||||
|
// struct that has an Rc to the same data is alive.
|
||||||
|
sys::ImPlot_LinkNextPlotLimits(
|
||||||
|
xmin_pointer,
|
||||||
|
xmax_pointer,
|
||||||
|
y_limit_pointers[0].0,
|
||||||
|
y_limit_pointers[0].1,
|
||||||
|
y_limit_pointers[1].0,
|
||||||
|
y_limit_pointers[1].1,
|
||||||
|
y_limit_pointers[2].0,
|
||||||
|
y_limit_pointers[2].1,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal helper function to set tick labels in case they are specified. This does the
|
/// Internal helper function to set tick labels in case they are specified. This does the
|
||||||
|
|
Loading…
Reference in a new issue