From 0af4203d4f4104664710698de8315efdfe55dcf5 Mon Sep 17 00:00:00 2001 From: 4bb4 <67376761+4bb4@users.noreply.github.com> Date: Sun, 18 Oct 2020 16:22:37 +0200 Subject: [PATCH] Added better Y axis handling and API coverage --- Cargo.toml | 2 +- README.md | 43 +++--- implot-examples/examples/line_plots.rs | 64 +++++++-- src/lib.rs | 90 +++++++++--- src/plot.rs | 187 +++++++++++++------------ 5 files changed, 248 insertions(+), 138 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71fba3e..17bf075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "implot" -version = "0.1.0" +version = "0.2.0" edition = "2018" authors = ["Sandro Merkli", "implot-rs contributors"] description = "Rust bindings to https://github.com/epezent/implot" diff --git a/README.md b/README.md index 7327dc7..ff3639b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ Rust bindings for [ImPlot](https://github.com/epezent/implot), built by running [bindgen](https://github.com/rust-lang/rust-bindgen) on [cimplot](https://github.com/cimgui/cimplot). The bindings are currently based on ImPlot version 0.7. See the status section below for -detailed information on implementation status. +detailed information on implementation status. + +**Important note:** As long as the code is pre-1.0 release, the API is expected to have +breaking changes between minor versions. Patch versions should be backwards compatible. +After 1.0, semver will be followed more properly. ![demo](demo.png) @@ -20,27 +24,20 @@ clone the repo, change into the `implot-examples` directory and try for example ``` ## Documentation -Since the crate is not released yet, the documentation is not hosted yet either. You -can build it yourself however by cloning this repo and then doing +For released versions, see +[![Docs.rs documentation](https://docs.rs/implot/badge.svg)](https://docs.rs/implot/). +Make sure you are looking at the right release, since the API is still changing. +For the master branch, can build it yourself however by cloning this repo and then doing ``` cargo doc --open ``` An effort is made to document everything as it is being added. Feel free to open an issue if documentation is unclear or lacking. -## Design approach -This repo tries to follow the approaches and style used in `imgui-rs` somewhat closely, -because implot is to be used within imgui programs, and hence keeping the interfaces -and design philosophies close should make it easier to do that. -If you spot any design inconsistencies or paper cuts, feel free to open an issue. - -## Status -Currently a work in progress. The author is open to collaboration, if you'd like to -help, feel free to reach out via a Github issue. - -Note that the API is not stabilized yet and expected to change as development progresses. -Once there are actual releases on crates.io, semantic versioning will be followed. +## Implementation status +Currently a work in progress, coverage of the C++ API is increased steadily. The author +is open to collaboration, if you'd like to help, feel free to reach out via a Github issue. At this point, raw bindings are working in implot-sys, and more idiomatic interfaces for plot creation as well a subset of the functionality for plots are implemented. @@ -69,20 +66,30 @@ for plot creation as well a subset of the functionality for plots are implemente - [x] Styling colors - [x] Styling variables - [x] Colormaps -- [ ] Plot querying +- [x] Plot querying - [x] is hovered - [x] mouse position in plot - [x] plot limits - [x] is queried - [x] get plot query - - [ ] Choice of y axis + - [x] are axes hovered + - [x] Choice of y axis - [ ] Utils - [x] Plot limit setting - [x] imgui-rs style safe push/pop stacks - [x] Plot tick setting - [ ] Input remapping - - [ ] Set Y axis setting for subsequent elements + - [x] Set Y axis setting for subsequent elements + - [ ] Set non-default Y axis ticks and labels - [ ] Plot position and size reading - [ ] Pixel to plot position - [ ] Plot to pixel position - [ ] Push/pop plotclip rect (?) + +# Developer documentation +## Design approach +This repo tries to follow the approaches and style used in `imgui-rs` somewhat closely, +because implot is to be used within imgui programs, and hence keeping the interfaces +and design philosophies close should make it easier to do that. + +If you spot any design inconsistencies or paper cuts, feel free to open an issue. diff --git a/implot-examples/examples/line_plots.rs b/implot-examples/examples/line_plots.rs index 5f9cdde..2e0a733 100644 --- a/implot-examples/examples/line_plots.rs +++ b/implot-examples/examples/line_plots.rs @@ -5,8 +5,9 @@ use imgui::{im_str, CollapsingHeader, Condition, Ui, Window}; use implot::{ get_plot_limits, get_plot_mouse_position, get_plot_query, is_plot_hovered, is_plot_queried, push_style_color, push_style_var_f32, push_style_var_i32, set_colormap_from_preset, - set_colormap_from_vec, AxisFlags, Colormap, Context, ImPlotLimits, ImPlotPoint, ImPlotRange, - ImVec4, Marker, Plot, PlotColorElement, PlotFlags, PlotLine, PlotUi, StyleVar, + set_colormap_from_vec, set_plot_y_axis, AxisFlags, Colormap, Context, ImPlotLimits, + ImPlotPoint, ImPlotRange, ImVec4, Marker, Plot, PlotColorElement, PlotFlags, PlotLine, PlotUi, + StyleVar, YAxisChoice, }; mod support; @@ -28,9 +29,43 @@ fn show_basic_plot(ui: &Ui, plot_ui: &PlotUi) { }); } +fn show_two_yaxis_plot(ui: &Ui, plot_ui: &PlotUi) { + ui.text(im_str!( + "This header shows how to create a plot with multiple Y axes." + )); + 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( + &ImPlotRange { Min: 1.0, Max: 3.5 }, + YAxisChoice::Second, + Condition::Always, + ) + .build(plot_ui, || { + let x_positions = vec![0.1, 0.9]; + + // The first Y axis is the default + 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 + set_plot_y_axis(YAxisChoice::Second); + let y_positions = vec![3.3, 1.2]; + PlotLine::new("legend label two").plot(&x_positions, &y_positions); + }); +} + fn show_configurable_plot(ui: &Ui, plot_ui: &PlotUi) { ui.text(im_str!( - "This header demos what we can configure about plots. €." + "This header demos what we can configure about plots." )); // Settings for the plot @@ -79,14 +114,15 @@ fn show_configurable_plot(ui: &Ui, plot_ui: &PlotUi) { Min: y_min, Max: y_max, }, + YAxisChoice::First, Condition::Always, ) .x_ticks(&x_ticks, false) - .y_ticks_with_labels(&y_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(&y_axis_flags) + .with_y_axis_flags(YAxisChoice::First, &y_axis_flags) .build(plot_ui, || { PlotLine::new("A line").plot(&vec![2.1, 2.9], &vec![1.1, 1.9]); }); @@ -107,17 +143,21 @@ fn show_query_features_plot(ui: &Ui, plot_ui: &PlotUi) { 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 }, 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 = Some(get_plot_mouse_position()); + hover_pos = Some(get_plot_mouse_position(None)); } if is_plot_queried() { - query_limits = Some(get_plot_query()); + query_limits = Some(get_plot_query(None)); } - plot_limits = Some(get_plot_limits()); + plot_limits = Some(get_plot_limits(None)); }); // Print some previously-exfiltrated info. This is because calling @@ -153,10 +193,11 @@ fn show_style_plot(ui: &Ui, plot_ui: &PlotUi) { Min: -1.0, Max: 3.0, }, + YAxisChoice::First, Condition::Always, ) .with_plot_flags(&(PlotFlags::NONE)) - .with_y_axis_flags(&(AxisFlags::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. @@ -268,6 +309,9 @@ fn main() { if CollapsingHeader::new(im_str!("Colormap selection")).build(&ui) { show_colormaps_plot(&ui, &plot_ui); } + if CollapsingHeader::new(im_str!("Multiple Y Axes")).build(&ui) { + show_two_yaxis_plot(&ui, &plot_ui); + } }); if showing_demo { diff --git a/src/lib.rs b/src/lib.rs index c207792..fbc0bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,28 @@ mod context; mod plot; mod plot_elements; +// The bindings for some reason don't contain this - it has to match the IMPLOT_AUTO from +// the original C++ header for things to work properly. +const IMPLOT_AUTO: i32 = -1; + +// Number of Y axes, this is used in a bunch of places for storing things like settings. +// If this changes, also change the YAxisChoice enum. +const NUMBER_OF_Y_AXES: usize = 3; + +/// Choice of Y axis. This an enum instead of just an integer so as to make it impossible +/// to select a Y axis that is not present - this makes it easier to avoid `Result`-type +/// return values on functions that could otherwise not really fail. +// Implementation note: This enum is converted straight to an usize index in a few places +// so we can store data about individual axes in arrays, so this pretty much should stay +// just a mapping of words to numbers. +#[derive(Clone)] +#[repr(i32)] +pub enum YAxisChoice { + First = 0, + Second = 1, + Third = 2, +} + /// A temporary reference for building plots. This does not really do anything on its own at /// this point, but it is used to enforce that a context is created and active for other features, /// such as creating plots. @@ -251,9 +273,6 @@ pub fn push_style_color( /// Tracks a change pushed to the style color stack pub struct StyleColorToken { /// Whether this token has been popped or not. - /// TODO(4bb4) figure out if it is a good idea to warn about this not being popped when it is - /// dropped - this may not be a good idea since users may want to push some style vars for - /// longer durations. was_popped: bool, } @@ -311,9 +330,6 @@ pub fn push_style_var_imvec2(element: &StyleVar, value: ImVec2) -> StyleVarToken /// Tracks a change pushed to the style variable stack pub struct StyleVarToken { /// Whether this token has been popped or not. - /// TODO(4bb4) figure out if it is a good idea to warn about this not being popped when it is - /// dropped - this may not be a good idea since users may want to push some style vars for - /// longer durations. was_popped: bool, } @@ -341,47 +357,79 @@ pub fn is_plot_queried() -> bool { unsafe { sys::ImPlot_IsPlotQueried() } } -/// Returns the mouse position in x,y coordinates of the current or most recent plot. Currently -/// pertains to whatever Y axis was most recently selected. TODO(4bb4) add y axis selection -pub fn get_plot_mouse_position() -> ImPlotPoint { - let y_axis_selection = 0; +/// Returns the mouse position in x,y coordinates of the current or most recent plot, +/// for the specified choice of Y axis. If `None` is the Y axis choice, that means the +/// most recently selected Y axis is chosen. +pub fn get_plot_mouse_position(y_axis_choice: Option) -> ImPlotPoint { + let y_axis_choice_i32 = match y_axis_choice { + Some(choice) => choice as i32, + None => IMPLOT_AUTO, + }; let mut point = ImPlotPoint { x: 0.0, y: 0.0 }; // doesn't seem to have default() unsafe { - sys::ImPlot_GetPlotMousePos(&mut point as *mut ImPlotPoint, y_axis_selection); + sys::ImPlot_GetPlotMousePos(&mut point as *mut ImPlotPoint, y_axis_choice_i32); } point } -/// Returns the current or most recent plot axis range. Currently pertains to whatever Y axis was -/// most recently selected. TODO(4bb4) add y axis selection -pub fn get_plot_limits() -> ImPlotLimits { - let y_axis_selection = 0; +/// Returns the current or most recent plot axis range for the specified choice of Y axis. If +/// `None` is the Y axis choice, that means the most recently selected Y axis is chosen. +pub fn get_plot_limits(y_axis_choice: Option) -> ImPlotLimits { + let y_axis_choice_i32 = match y_axis_choice { + Some(choice) => choice as i32, + None => IMPLOT_AUTO, + }; // ImPlotLimits doesn't seem to have default() let mut limits = ImPlotLimits { X: ImPlotRange { Min: 0.0, Max: 0.0 }, Y: ImPlotRange { Min: 0.0, Max: 0.0 }, }; unsafe { - sys::ImPlot_GetPlotLimits(&mut limits as *mut ImPlotLimits, y_axis_selection); + sys::ImPlot_GetPlotLimits(&mut limits as *mut ImPlotLimits, y_axis_choice_i32); } limits } -/// Returns the query limits of the current or most recent plot. Currently pertains to whatever Y -/// axis was most recently selected. TODO(4bb4) add y axis selection -pub fn get_plot_query() -> ImPlotLimits { - let y_axis_selection = 0; +/// Returns the query limits of the current or most recent plot, for the specified choice of Y +/// axis. If `None` is the Y axis choice, that means the most recently selected Y axis is chosen. +pub fn get_plot_query(y_axis_choice: Option) -> ImPlotLimits { + let y_axis_choice_i32 = match y_axis_choice { + Some(choice) => choice as i32, + None => IMPLOT_AUTO, + }; // ImPlotLimits doesn't seem to have default() let mut limits = ImPlotLimits { X: ImPlotRange { Min: 0.0, Max: 0.0 }, Y: ImPlotRange { Min: 0.0, Max: 0.0 }, }; unsafe { - sys::ImPlot_GetPlotQuery(&mut limits as *mut ImPlotLimits, y_axis_selection); + sys::ImPlot_GetPlotQuery(&mut limits as *mut ImPlotLimits, y_axis_choice_i32); } limits } +/// Set the Y axis to be used for any upcoming plot elements +pub fn set_plot_y_axis(y_axis_choice: YAxisChoice) { + unsafe { + sys::ImPlot_SetPlotYAxis(y_axis_choice as i32); + } +} + +/// Returns true if the XAxis plot area in the current plot is hovered. +pub fn is_plot_x_axis_hovered() -> bool { + unsafe { sys::ImPlot_IsPlotXAxisHovered() } +} + +/// Returns true if the YAxis[n] plot area in the current plot is hovered. If `None` is the Y axis +/// choice, that means the most recently selected Y axis is chosen. +pub fn is_plot_y_axis_hovered(y_axis_choice: Option) -> bool { + let y_axis_choice_i32 = match y_axis_choice { + Some(choice) => choice as i32, + None => IMPLOT_AUTO, + }; + unsafe { sys::ImPlot_IsPlotYAxisHovered(y_axis_choice_i32) } +} + // --- Demo window ------------------------------------------------------------------------------- /// Show the demo window for poking around what functionality implot has to /// offer. Note that not all of this is necessarily implemented in implot-rs diff --git a/src/plot.rs b/src/plot.rs index a8d20ab..e978dfd 100644 --- a/src/plot.rs +++ b/src/plot.rs @@ -8,7 +8,7 @@ pub use sys::imgui::Condition; use sys::imgui::{im_str, ImString}; pub use sys::{ImPlotLimits, ImPlotPoint, ImPlotRange, ImVec2, ImVec4}; -use crate::{Context, PlotUi}; +use crate::{Context, PlotUi, YAxisChoice, NUMBER_OF_Y_AXES}; const DEFAULT_PLOT_SIZE_X: f32 = 400.0; const DEFAULT_PLOT_SIZE_Y: f32 = 400.0; @@ -107,11 +107,11 @@ pub struct Plot { /// X axis limits, if present x_limits: Option, /// Y axis limits, if present - y_limits: Option, + y_limits: [Option; NUMBER_OF_Y_AXES], /// Condition on which the x limits are set x_limit_condition: Option, - /// Condition on which the y limits are set (first y axis for now) - y_limit_condition: Option, + /// Condition on which the y limits are set for each of the axes + y_limit_condition: [Option; NUMBER_OF_Y_AXES], /// Positions for custom X axis ticks, if any x_tick_positions: Option>, /// Labels for custom X axis ticks, if any. I'd prefer to store these together @@ -124,33 +124,31 @@ pub struct Plot { /// Whether to also show the default X ticks when showing custom ticks or not show_x_default_ticks: bool, /// Positions for custom Y axis ticks, if any - y_tick_positions: Option>, + y_tick_positions: [Option>; NUMBER_OF_Y_AXES], /// Labels for custom Y axis ticks, if any. I'd prefer to store these together /// with the positions in one vector of an algebraic data type, but this would mean extra /// copies when it comes time to draw the plot because the C++ library expects separate lists. /// The data is stored as ImStrings because those are null-terminated, and since we have to /// convert to null-terminated data anyway, we may as well do that directly instead of cloning /// Strings and converting them afterwards. - y_tick_labels: Option>, + y_tick_labels: [Option>; NUMBER_OF_Y_AXES], /// Whether to also show the default Y ticks when showing custom ticks or not - show_y_default_ticks: bool, + show_y_default_ticks: [bool; NUMBER_OF_Y_AXES], /// Flags relating to the plot TODO(4bb4) make those into bitflags plot_flags: sys::ImPlotFlags, - /// Flags relating to the first x axis of the plot TODO(4bb4) make those into bitflags + /// Flags relating to the X axis of the plot TODO(4bb4) make those into bitflags x_flags: sys::ImPlotAxisFlags, - /// Flags relating to the first y axis of the plot TODO(4bb4) make those into bitflags - y_flags: sys::ImPlotAxisFlags, - /// Flags relating to the second y axis of the plot (if present, otherwise ignored) - /// TODO(4bb4) make those into bitflags - y2_flags: sys::ImPlotAxisFlags, - /// Flags relating to the third y axis of the plot (if present, otherwise ignored) - /// TODO(4bb4) make those into bitflags - y3_flags: sys::ImPlotAxisFlags, + /// Flags relating to the each of the Y axes of the plot TODO(4bb4) make those into bitflags + y_flags: [sys::ImPlotAxisFlags; NUMBER_OF_Y_AXES], } impl Plot { /// Create a new plot with some defaults set. Does not draw anything yet. pub fn new(title: &str) -> Self { + // Needed for initialization, see https://github.com/rust-lang/rust/issues/49147 + const POS_NONE: Option> = None; + const TICK_NONE: Option> = None; + // TODO(4bb4) question these defaults, maybe remove some of them Self { title: im_str!("{}", title), @@ -159,20 +157,18 @@ impl Plot { x_label: im_str!("").into(), y_label: im_str!("").into(), x_limits: None, - y_limits: None, + y_limits: [None; NUMBER_OF_Y_AXES], x_limit_condition: None, - y_limit_condition: None, + y_limit_condition: [None; NUMBER_OF_Y_AXES], x_tick_positions: None, x_tick_labels: None, show_x_default_ticks: false, - y_tick_positions: None, - y_tick_labels: None, - show_y_default_ticks: false, + y_tick_positions: [POS_NONE; NUMBER_OF_Y_AXES], + y_tick_labels: [TICK_NONE; NUMBER_OF_Y_AXES], + show_y_default_ticks: [false; NUMBER_OF_Y_AXES], plot_flags: PlotFlags::NONE.bits() as sys::ImPlotFlags, x_flags: AxisFlags::NONE.bits() as sys::ImPlotAxisFlags, - y_flags: AxisFlags::NONE.bits() as sys::ImPlotAxisFlags, - y2_flags: AxisFlags::NONE.bits() as sys::ImPlotAxisFlags, - y3_flags: AxisFlags::NONE.bits() as sys::ImPlotAxisFlags, + y_flags: [AxisFlags::NONE.bits() as sys::ImPlotAxisFlags; NUMBER_OF_Y_AXES], } } @@ -207,11 +203,18 @@ impl Plot { self } - /// Set the y limits of the plot + /// Set the Y limits of the plot for the given Y axis. Call multiple times + /// to set for multiple axes. #[inline] - pub fn y_limits(mut self, limits: &ImPlotRange, condition: Condition) -> Self { - self.y_limits = Some(*limits); - self.y_limit_condition = Some(condition); + pub fn y_limits( + mut self, + limits: &ImPlotRange, + y_axis_choice: YAxisChoice, + condition: Condition, + ) -> Self { + let axis_index = y_axis_choice as usize; + self.y_limits[axis_index] = Some(*limits); + self.y_limit_condition[axis_index] = Some(condition); self } @@ -229,9 +232,15 @@ impl Plot { /// the form of a tuple `(label_position, label_string)`. The `show_default` setting /// determines whether the default ticks are also shown. #[inline] - pub fn y_ticks(mut self, ticks: &Vec, show_default: bool) -> Self { - self.y_tick_positions = Some(ticks.clone()); - self.show_y_default_ticks = show_default; + pub fn y_ticks( + mut self, + y_axis_choice: YAxisChoice, + ticks: &Vec, + show_default: bool, + ) -> Self { + let axis_index = y_axis_choice as usize; + self.y_tick_positions[axis_index] = Some(ticks.clone()); + self.show_y_default_ticks[axis_index] = show_default; self } @@ -256,12 +265,15 @@ impl Plot { #[inline] pub fn y_ticks_with_labels( mut self, + y_axis_choice: YAxisChoice, tick_labels: &Vec<(f64, String)>, show_default: bool, ) -> Self { - self.y_tick_positions = Some(tick_labels.iter().map(|x| x.0).collect()); - self.y_tick_labels = Some(tick_labels.iter().map(|x| im_str!("{}", x.1)).collect()); - self.show_y_default_ticks = show_default; + let axis_index = y_axis_choice as usize; + self.y_tick_positions[axis_index] = Some(tick_labels.iter().map(|x| x.0).collect()); + self.y_tick_labels[axis_index] = + Some(tick_labels.iter().map(|x| im_str!("{}", x.1)).collect()); + self.show_y_default_ticks[axis_index] = show_default; self } @@ -279,24 +291,11 @@ impl Plot { self } - /// Set the axis flags for the first Y axis in this plot + /// Set the axis flags for the selected Y axis in this plot #[inline] - pub fn with_y_axis_flags(mut self, flags: &AxisFlags) -> Self { - self.y_flags = flags.bits() as sys::ImPlotAxisFlags; - self - } - - /// Set the axis flags for the second Y axis in this plot - #[inline] - pub fn with_y2_axis_flags(mut self, flags: &AxisFlags) -> Self { - self.y2_flags = flags.bits() as sys::ImPlotAxisFlags; - self - } - - /// Set the axis flags for the third Y axis in this plot - #[inline] - pub fn with_y3_axis_flags(mut self, flags: &AxisFlags) -> Self { - self.y3_flags = flags.bits() as sys::ImPlotAxisFlags; + pub fn with_y_axis_flags(mut self, y_axis_choice: YAxisChoice, flags: &AxisFlags) -> Self { + let axis_index = y_axis_choice as usize; + self.y_flags[axis_index] = flags.bits() as sys::ImPlotAxisFlags; self } @@ -309,19 +308,23 @@ impl Plot { } } - // Set X limits if specified - if let (Some(limits), Some(condition)) = (self.y_limits, self.y_limit_condition) { - // TODO(4bb4) allow for specification of multiple y limits, not just the first - let selected_y_axis = 0; - unsafe { - sys::ImPlot_SetNextPlotLimitsY( - limits.Min, - limits.Max, - condition as sys::ImGuiCond, - selected_y_axis, - ); - } - } + // Set Y limits if specified + self.y_limits + .iter() + .zip(self.y_limit_condition.iter()) + .enumerate() + .for_each(|(k, (limits, condition))| { + if let (Some(limits), Some(condition)) = (limits, condition) { + unsafe { + sys::ImPlot_SetNextPlotLimitsY( + limits.Min, + limits.Max, + *condition as sys::ImGuiCond, + k as i32, + ); + } + } + }); } /// Internal helper function to set tick labels in case they are specified. This does the @@ -351,28 +354,36 @@ impl Plot { } } - if self.y_tick_positions.is_some() && self.y_tick_positions.as_ref().unwrap().len() > 0 { - let mut pointer_vec; // The vector of pointers we create has to have a longer lifetime - let labels_pointer = if let Some(labels_value) = &self.y_tick_labels { - pointer_vec = labels_value - .iter() - .map(|x| x.as_ptr() as *const i8) - .collect::>(); - pointer_vec.as_mut_ptr() - } else { - std::ptr::null_mut() - }; + self.y_tick_positions + .iter() + .zip(self.y_tick_labels.iter()) + .zip(self.show_y_default_ticks.iter()) + .enumerate() + .for_each(|(k, ((positions, labels), show_defaults))| { + if positions.is_some() && positions.as_ref().unwrap().len() > 0 { + // The vector of pointers we create has to have a longer lifetime + let mut pointer_vec; + let labels_pointer = if let Some(labels_value) = &labels { + pointer_vec = labels_value + .iter() + .map(|x| x.as_ptr() as *const i8) + .collect::>(); + pointer_vec.as_mut_ptr() + } else { + std::ptr::null_mut() + }; - unsafe { - sys::ImPlot_SetNextPlotTicksYdoublePtr( - self.y_tick_positions.as_ref().unwrap().as_ptr(), - self.y_tick_positions.as_ref().unwrap().len() as i32, - labels_pointer, - self.show_y_default_ticks, - 0, // y axis selection, TODO(4bb4) make this configurable - ) - } - } + unsafe { + sys::ImPlot_SetNextPlotTicksYdoublePtr( + positions.as_ref().unwrap().as_ptr(), + positions.as_ref().unwrap().len() as i32, + labels_pointer, + *show_defaults, + k as i32, + ) + } + } + }); } /// Attempt to show the plot. If this returns a token, the plot will actually @@ -397,9 +408,9 @@ impl Plot { }, self.plot_flags, self.x_flags, - self.y_flags, - self.y2_flags, - self.y3_flags, + self.y_flags[0], + self.y_flags[1], + self.y_flags[2], ) };