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:
4bb4 2021-05-30 11:45:16 +02:00
parent ad80781f4d
commit 06cc3061c1
5 changed files with 570 additions and 391 deletions

View file

@ -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);
}); });
}
} }

View file

@ -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);
}
} }
} }

View file

@ -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!"))

View file

@ -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!"))

View file

@ -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