Refactoring code into smaller files
This commit is contained in:
parent
d717af71fa
commit
74833663a9
4 changed files with 705 additions and 682 deletions
49
src/context.rs
Normal file
49
src/context.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// TODO(4bb4) Do this properly.
|
||||||
|
// I already added a simple Context struct that can be created once and used as long as it is not
|
||||||
|
// dropped here for initial tests - this is of course neither threadsafe nor otherwise safe to use
|
||||||
|
// unless one "does it right", so it's not a real solution.
|
||||||
|
//
|
||||||
|
// The context should have to be created, and ideally it should be difficult to impossible
|
||||||
|
// to do things without having a context. implot-rs makes it so that there is a context and
|
||||||
|
// that context has a "frame()" function that returns a Ui, and that Ui is then used to create
|
||||||
|
// widgets. Windows are built with a build() function that takes a reference to that Ui as an
|
||||||
|
// argument, but also have a begin() function that take a context and put it in their token.
|
||||||
|
//
|
||||||
|
// I think I'll mirror that here, except that we don't need a frame() function, it's enough
|
||||||
|
// to create a context once and then keep passing it around. I'll hence need a mutex and
|
||||||
|
// a mechansim similar (or equal) to what imgui-rs does for making sure there can only be
|
||||||
|
// a single context. Implementation could go roughly like this:
|
||||||
|
//
|
||||||
|
// - Add a mutex for modifying context things
|
||||||
|
// - Make creation and drop functions use that mutex
|
||||||
|
// - Change Plot, PlotLine, PlotScatter, PlotBars, PlotText to all require a context.
|
||||||
|
// I think I'll call this PlotUi to mimmick imgui-rs' Ui.
|
||||||
|
// - Think about what this means in terms of the stacks and things like is_plot_hovered() -
|
||||||
|
// they should also only work when there is a context available.
|
||||||
|
//
|
||||||
|
/// An implot context.
|
||||||
|
///
|
||||||
|
/// A context is required to do most of the things this library provides. While this was created
|
||||||
|
/// implicitly in earlier versions of the library, it is now created explicitly.
|
||||||
|
pub struct Context {
|
||||||
|
raw: *mut sys::ImPlotContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
/// Create a context.
|
||||||
|
pub fn create() -> Self {
|
||||||
|
let ctx = unsafe { sys::ImPlot_CreateContext() };
|
||||||
|
unsafe {
|
||||||
|
sys::ImPlot_SetCurrentContext(ctx);
|
||||||
|
}
|
||||||
|
Self { raw: ctx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Context {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
sys::ImPlot_DestroyContext(self.raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
690
src/lib.rs
690
src/lib.rs
|
@ -8,80 +8,23 @@
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
pub extern crate implot_sys as sys;
|
pub extern crate implot_sys as sys;
|
||||||
use bitflags::bitflags;
|
|
||||||
pub use sys::imgui::Condition;
|
pub use sys::imgui::Condition;
|
||||||
use sys::imgui::{im_str, ImString};
|
|
||||||
// TODO(4bb4) facade-wrap these
|
// TODO(4bb4) facade-wrap these
|
||||||
pub use sys::{ImPlotLimits, ImPlotPoint, ImPlotRange, ImVec2, ImVec4};
|
pub use sys::{ImPlotLimits, ImPlotPoint, ImPlotRange, ImVec2, ImVec4};
|
||||||
|
|
||||||
const DEFAULT_PLOT_SIZE_X: f32 = 400.0;
|
// Plot struct and associated enums
|
||||||
const DEFAULT_PLOT_SIZE_Y: f32 = 400.0;
|
pub mod context;
|
||||||
|
pub mod plot;
|
||||||
|
pub mod plot_elements;
|
||||||
|
|
||||||
|
pub use context::Context;
|
||||||
|
pub use plot::{AxisFlags, Plot, PlotFlags};
|
||||||
|
pub use plot_elements::{PlotBars, PlotLine, PlotScatter, PlotText};
|
||||||
|
|
||||||
// --- Enum definitions --------------------------------------------------------------------------
|
// --- Enum definitions --------------------------------------------------------------------------
|
||||||
// Things that are to be combined like flags are done using bitflags, and things that are meant
|
// Things that are to be combined like flags are done using bitflags, and things that are meant
|
||||||
// as enumerations in the traditional sense are plain enums.
|
// as enumerations in the traditional sense are plain enums.
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
/// Flags for customizing plot behavior and interaction. Documentation copied from implot.h for
|
|
||||||
/// convenience. ImPlot itself also has a "CanvasOnly" flag, which can be emulated here with
|
|
||||||
/// the combination of `NO_LEGEND`, `NO_MENUS`, `NO_BOX_SELECT` and `NO_MOUSE_POSITION`.
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct PlotFlags: u32 {
|
|
||||||
/// "Default" according to original docs
|
|
||||||
const NONE = sys::ImPlotFlags__ImPlotFlags_None;
|
|
||||||
/// Plot items will not be highlighted when their legend entry is hovered
|
|
||||||
const NO_LEGEND = sys::ImPlotFlags__ImPlotFlags_NoLegend;
|
|
||||||
/// The user will not be able to open context menus with double-right click
|
|
||||||
const NO_MENUS = sys::ImPlotFlags__ImPlotFlags_NoMenus;
|
|
||||||
/// The user will not be able to box-select with right-mouse
|
|
||||||
const NO_BOX_SELECT = sys::ImPlotFlags__ImPlotFlags_NoBoxSelect;
|
|
||||||
/// The mouse position, in plot coordinates, will not be displayed
|
|
||||||
const NO_MOUSE_POSITION = sys::ImPlotFlags__ImPlotFlags_NoMousePos;
|
|
||||||
/// Plot items will not be highlighted when their legend entry is hovered
|
|
||||||
const NO_HIGHLIGHT = sys::ImPlotFlags__ImPlotFlags_NoHighlight;
|
|
||||||
/// A child window region will not be used to capture mouse scroll (can boost performance
|
|
||||||
/// for single ImGui window applications)
|
|
||||||
const NO_CHILD = sys::ImPlotFlags__ImPlotFlags_NoChild;
|
|
||||||
/// Enable a 2nd y axis
|
|
||||||
const Y_AXIS_2 = sys::ImPlotFlags__ImPlotFlags_YAxis2;
|
|
||||||
/// Enable a 3nd y axis
|
|
||||||
const Y_AXIS_3 = sys::ImPlotFlags__ImPlotFlags_YAxis3;
|
|
||||||
/// The user will be able to draw query rects with middle-mouse
|
|
||||||
const QUERY = sys::ImPlotFlags__ImPlotFlags_Query;
|
|
||||||
/// The default mouse cursor will be replaced with a crosshair when hovered
|
|
||||||
const CROSSHAIRS = sys::ImPlotFlags__ImPlotFlags_Crosshairs;
|
|
||||||
/// Plot data outside the plot area will be culled from rendering
|
|
||||||
const ANTIALIASED = sys::ImPlotFlags__ImPlotFlags_AntiAliased;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
/// Axis flags. Documentation copied from implot.h for convenience. ImPlot itself also
|
|
||||||
/// has `Lock`, which combines `LOCK_MIN` and `LOCK_MAX`, and `NoDecorations`, which combines
|
|
||||||
/// `NO_GRID_LINES`, `NO_TICK_MARKS` and `NO_TICK_LABELS`.
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct AxisFlags: u32 {
|
|
||||||
/// "Default" according to original docs
|
|
||||||
const NONE = sys::ImPlotAxisFlags__ImPlotAxisFlags_None;
|
|
||||||
/// Grid lines will not be displayed
|
|
||||||
const NO_GRID_LINES = sys::ImPlotAxisFlags__ImPlotAxisFlags_NoGridLines;
|
|
||||||
/// Tick marks will not be displayed
|
|
||||||
const NO_TICK_MARKS = sys::ImPlotAxisFlags__ImPlotAxisFlags_NoTickMarks;
|
|
||||||
/// Text labels will not be displayed
|
|
||||||
const NO_TICK_LABELS = sys::ImPlotAxisFlags__ImPlotAxisFlags_NoTickLabels;
|
|
||||||
/// A logartithmic (base 10) axis scale will be used (mutually exclusive with AxisFlags::TIME)
|
|
||||||
const LOG_SCALE = sys::ImPlotAxisFlags__ImPlotAxisFlags_LogScale;
|
|
||||||
/// Axis will display date/time formatted labels (mutually exclusive with AxisFlags::LOG_SCALE)
|
|
||||||
const TIME = sys::ImPlotAxisFlags__ImPlotAxisFlags_Time;
|
|
||||||
/// The axis will be inverted
|
|
||||||
const INVERT = sys::ImPlotAxisFlags__ImPlotAxisFlags_Invert;
|
|
||||||
/// The axis minimum value will be locked when panning/zooming
|
|
||||||
const LOCK_MIN = sys::ImPlotAxisFlags__ImPlotAxisFlags_LockMin;
|
|
||||||
/// The axis maximum value will be locked when panning/zooming
|
|
||||||
const LOCK_MAX = sys::ImPlotAxisFlags__ImPlotAxisFlags_LockMax;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Markers, documentation copied from implot.h for convenience.
|
/// Markers, documentation copied from implot.h for convenience.
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
@ -244,625 +187,8 @@ pub enum StyleVar {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Context -----------------------------------------------------------------------------------
|
// --- Context -----------------------------------------------------------------------------------
|
||||||
// TODO(4bb4) Do this properly.
|
|
||||||
// I already added a simple Context struct that can be created once and used as long as it is not
|
|
||||||
// dropped here for initial tests - this is of course neither threadsafe nor otherwise safe to use
|
|
||||||
// unless one "does it right", so it's not a real solution.
|
|
||||||
//
|
|
||||||
// The context should have to be created, and ideally it should be difficult to impossible
|
|
||||||
// to do things without having a context. implot-rs makes it so that there is a context and
|
|
||||||
// that context has a "frame()" function that returns a Ui, and that Ui is then used to create
|
|
||||||
// widgets. Windows are built with a build() function that takes a reference to that Ui as an
|
|
||||||
// argument, but also have a begin() function that take a context and put it in their token.
|
|
||||||
//
|
|
||||||
// I think I'll mirror that here, except that we don't need a frame() function, it's enough
|
|
||||||
// to create a context once and then keep passing it around. I'll hence need a mutex and
|
|
||||||
// a mechansim similar (or equal) to what imgui-rs does for making sure there can only be
|
|
||||||
// a single context. Implementation could go roughly like this:
|
|
||||||
//
|
|
||||||
// - Add a mutex for modifying context things
|
|
||||||
// - Make creation and drop functions use that mutex
|
|
||||||
// - Change Plot, PlotLine, PlotScatter, PlotBars, PlotText to all require a context.
|
|
||||||
// I think I'll call this PlotUi to mimmick imgui-rs' Ui.
|
|
||||||
// - Think about what this means in terms of the stacks and things like is_plot_hovered() -
|
|
||||||
// they should also only work when there is a context available.
|
|
||||||
//
|
|
||||||
pub struct Context {
|
|
||||||
raw: *mut sys::ImPlotContext,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
|
||||||
pub fn create() -> Self {
|
|
||||||
let ctx = unsafe { sys::ImPlot_CreateContext() };
|
|
||||||
unsafe {
|
|
||||||
sys::ImPlot_SetCurrentContext(ctx);
|
|
||||||
}
|
|
||||||
Self { raw: ctx }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Context {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
sys::ImPlot_DestroyContext(self.raw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Main plot structure -----------------------------------------------------------------------
|
// --- Main plot structure -----------------------------------------------------------------------
|
||||||
/// 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:
|
|
||||||
/// ```no_run
|
|
||||||
/// # use implot;
|
|
||||||
/// implot::Plot::new("my title")
|
|
||||||
/// .size(300.0, 200.0) // other things such as .x_label("some_label") can be added too
|
|
||||||
/// .build( || {
|
|
||||||
/// // Do things such as plotting lines
|
|
||||||
/// });
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// (If you are coming from the C++ implementation or the C bindings: build() calls both
|
|
||||||
/// begin() and end() internally)
|
|
||||||
pub struct Plot {
|
|
||||||
/// Title of the plot, shown on top.
|
|
||||||
title: String,
|
|
||||||
/// Size of the plot in x direction, in the same units imgui uses.
|
|
||||||
size_x: f32,
|
|
||||||
/// Size of the plot in y direction, in the same units imgui uses.
|
|
||||||
size_y: f32,
|
|
||||||
/// Label of the x axis, shown on the bottom
|
|
||||||
x_label: String,
|
|
||||||
/// Label of the y axis, shown on the left
|
|
||||||
y_label: String,
|
|
||||||
/// X axis limits, if present
|
|
||||||
x_limits: Option<ImPlotRange>,
|
|
||||||
/// Y axis limits, if present
|
|
||||||
y_limits: Option<ImPlotRange>,
|
|
||||||
/// Condition on which the x limits are set
|
|
||||||
x_limit_condition: Option<Condition>,
|
|
||||||
/// Condition on which the y limits are set (first y axis for now)
|
|
||||||
y_limit_condition: Option<Condition>,
|
|
||||||
/// Positions for custom X axis ticks, if any
|
|
||||||
x_tick_positions: Option<Vec<f64>>,
|
|
||||||
/// Labels for custom X 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.
|
|
||||||
x_tick_labels: Option<Vec<ImString>>,
|
|
||||||
/// 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<Vec<f64>>,
|
|
||||||
/// 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<Vec<ImString>>,
|
|
||||||
/// Whether to also show the default Y ticks when showing custom ticks or not
|
|
||||||
show_y_default_ticks: bool,
|
|
||||||
/// 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
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plot {
|
|
||||||
/// Create a new plot with some defaults set. Does not draw anything yet.
|
|
||||||
pub fn new(title: &str) -> Self {
|
|
||||||
// TODO(4bb4) question these defaults, maybe remove some of them
|
|
||||||
Self {
|
|
||||||
title: title.to_owned(),
|
|
||||||
size_x: DEFAULT_PLOT_SIZE_X,
|
|
||||||
size_y: DEFAULT_PLOT_SIZE_Y,
|
|
||||||
x_label: "".to_owned(),
|
|
||||||
y_label: "".to_owned(),
|
|
||||||
x_limits: None,
|
|
||||||
y_limits: None,
|
|
||||||
x_limit_condition: None,
|
|
||||||
y_limit_condition: None,
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the plot size, given as [size_x, size_y]. Units are the same as
|
|
||||||
/// what imgui uses. TODO(4b4) ... which is? I'm not sure it's pixels
|
|
||||||
#[inline]
|
|
||||||
pub fn size(mut self, size_x: f32, size_y: f32) -> Self {
|
|
||||||
self.size_x = size_x;
|
|
||||||
self.size_y = size_y;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the x label of the plot
|
|
||||||
#[inline]
|
|
||||||
pub fn x_label(mut self, label: &str) -> Self {
|
|
||||||
self.x_label = label.to_owned();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the y label of the plot
|
|
||||||
#[inline]
|
|
||||||
pub fn y_label(mut self, label: &str) -> Self {
|
|
||||||
self.y_label = label.to_owned();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the x limits of the plot
|
|
||||||
#[inline]
|
|
||||||
pub fn x_limits(mut self, limits: &ImPlotRange, condition: Condition) -> Self {
|
|
||||||
self.x_limits = Some(*limits);
|
|
||||||
self.x_limit_condition = Some(condition);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the y limits of the plot
|
|
||||||
#[inline]
|
|
||||||
pub fn y_limits(mut self, limits: &ImPlotRange, condition: Condition) -> Self {
|
|
||||||
self.y_limits = Some(*limits);
|
|
||||||
self.y_limit_condition = Some(condition);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
/// determines whether the default ticks are also shown.
|
|
||||||
#[inline]
|
|
||||||
pub fn x_ticks(mut self, ticks: &Vec<f64>, show_default: bool) -> Self {
|
|
||||||
self.x_tick_positions = Some(ticks.clone());
|
|
||||||
self.show_x_default_ticks = show_default;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
/// determines whether the default ticks are also shown.
|
|
||||||
#[inline]
|
|
||||||
pub fn y_ticks(mut self, ticks: &Vec<f64>, show_default: bool) -> Self {
|
|
||||||
self.y_tick_positions = Some(ticks.clone());
|
|
||||||
self.show_y_default_ticks = show_default;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set X ticks with labels for the plot. The vector contains one position and label
|
|
||||||
/// each in the form of a tuple `(label_position, label_string)`. The `show_default`
|
|
||||||
/// setting determines whether the default ticks are also shown.
|
|
||||||
#[inline]
|
|
||||||
pub fn x_ticks_with_labels(
|
|
||||||
mut self,
|
|
||||||
tick_labels: &Vec<(f64, String)>,
|
|
||||||
show_default: bool,
|
|
||||||
) -> Self {
|
|
||||||
self.x_tick_positions = Some(tick_labels.iter().map(|x| x.0).collect());
|
|
||||||
self.x_tick_labels = Some(tick_labels.iter().map(|x| im_str!("{}", x.1)).collect());
|
|
||||||
self.show_x_default_ticks = show_default;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set Y ticks with labels for the plot. The vector contains one position and label
|
|
||||||
/// each in 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_with_labels(
|
|
||||||
mut self,
|
|
||||||
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;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the plot flags, see the help for `PlotFlags` for what the available flags are
|
|
||||||
#[inline]
|
|
||||||
pub fn with_plot_flags(mut self, flags: &PlotFlags) -> Self {
|
|
||||||
self.plot_flags = flags.bits() as sys::ImPlotFlags;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the axis flags for the X axis in this plot
|
|
||||||
#[inline]
|
|
||||||
pub fn with_x_axis_flags(mut self, flags: &AxisFlags) -> Self {
|
|
||||||
self.x_flags = flags.bits() as sys::ImPlotAxisFlags;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the axis flags for the first 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;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal helper function to set axis limits in case they are specified.
|
|
||||||
fn maybe_set_axis_limits(&self) {
|
|
||||||
// Set X limits if specified
|
|
||||||
if let (Some(limits), Some(condition)) = (self.x_limits, self.x_limit_condition) {
|
|
||||||
unsafe {
|
|
||||||
sys::ImPlot_SetNextPlotLimitsX(limits.Min, limits.Max, condition as sys::ImGuiCond);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal helper function to set tick labels in case they are specified. This does the
|
|
||||||
/// preparation work that is the same for both the X and Y axis plots, then calls the
|
|
||||||
/// "set next plot ticks" wrapper functions for both X and Y.
|
|
||||||
fn maybe_set_tick_labels(&self) {
|
|
||||||
// Show x ticks if they are available
|
|
||||||
if self.x_tick_positions.is_some() && self.x_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.x_tick_labels {
|
|
||||||
pointer_vec = labels_value
|
|
||||||
.iter()
|
|
||||||
.map(|x| x.as_ptr() as *const i8)
|
|
||||||
.collect::<Vec<*const i8>>();
|
|
||||||
pointer_vec.as_mut_ptr()
|
|
||||||
} else {
|
|
||||||
std::ptr::null_mut()
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
sys::ImPlot_SetNextPlotTicksXdoublePtr(
|
|
||||||
self.x_tick_positions.as_ref().unwrap().as_ptr(),
|
|
||||||
self.x_tick_positions.as_ref().unwrap().len() as i32,
|
|
||||||
labels_pointer,
|
|
||||||
self.show_x_default_ticks,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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::<Vec<*const i8>>();
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to show the plot. If this returns a token, the plot will actually
|
|
||||||
/// be drawn. In this case, use the drawing functionality to draw things on the
|
|
||||||
/// plot, and then call `end()` on the token when done with the plot.
|
|
||||||
/// If none was returned, that means the plot is not rendered.
|
|
||||||
///
|
|
||||||
/// For a convenient implementation of all this, use [`build()`](struct.Plot.html#method.build)
|
|
||||||
/// instead.
|
|
||||||
pub fn begin(&self) -> Option<PlotToken> {
|
|
||||||
self.maybe_set_axis_limits();
|
|
||||||
self.maybe_set_tick_labels();
|
|
||||||
|
|
||||||
let should_render = unsafe {
|
|
||||||
sys::ImPlot_BeginPlot(
|
|
||||||
im_str!("{}", self.title).as_ptr(),
|
|
||||||
im_str!("{}", self.x_label).as_ptr(),
|
|
||||||
im_str!("{}", self.y_label).as_ptr(),
|
|
||||||
sys::ImVec2 {
|
|
||||||
x: self.size_x as f32,
|
|
||||||
y: self.size_y as f32,
|
|
||||||
},
|
|
||||||
self.plot_flags,
|
|
||||||
self.x_flags,
|
|
||||||
self.y_flags,
|
|
||||||
self.y2_flags,
|
|
||||||
self.y3_flags,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_render {
|
|
||||||
Some(PlotToken {
|
|
||||||
plot_title: self.title.clone(),
|
|
||||||
has_ended: false,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// In contrast with imgui windows, end() does not have to be
|
|
||||||
// called if we don't render. This is more like an imgui popup modal.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a window and runs a closure to construct the contents.
|
|
||||||
///
|
|
||||||
/// Note: the closure is not called if ImPlot::BeginPlot() returned
|
|
||||||
/// false - TODO(4bb4) figure out if this is if things are not rendered
|
|
||||||
pub fn build<F: FnOnce()>(self, f: F) {
|
|
||||||
if let Some(token) = self.begin() {
|
|
||||||
f();
|
|
||||||
token.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tracks a plot that must be ended by calling `.end()`
|
|
||||||
pub struct PlotToken {
|
|
||||||
/// For better error messages
|
|
||||||
plot_title: String,
|
|
||||||
/// Whether end() has been called on this already or not
|
|
||||||
has_ended: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotToken {
|
|
||||||
/// End a previously begin()'ed plot.
|
|
||||||
pub fn end(mut self) {
|
|
||||||
self.has_ended = true;
|
|
||||||
unsafe { sys::ImPlot_EndPlot() };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for PlotToken {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if !self.has_ended && !std::thread::panicking() {
|
|
||||||
panic!(
|
|
||||||
"Warning: A PlotToken for plot \"{}\" was not called end() on",
|
|
||||||
self.plot_title
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Actual plotting functionality -------------------------------------------------------------
|
|
||||||
/// Struct to provide functionality for plotting a line in a plot.
|
|
||||||
pub struct PlotLine {
|
|
||||||
/// Label to show in the legend for this line
|
|
||||||
label: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotLine {
|
|
||||||
/// Create a new line to be plotted. Does not draw anything yet.
|
|
||||||
pub fn new(label: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
label: label.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Plot a line. Use this in closures passed to [`Plot::build()`](struct.Plot.html#method.build)
|
|
||||||
pub fn plot(&self, x: &Vec<f64>, y: &Vec<f64>) {
|
|
||||||
// If there is no data to plot, we stop here
|
|
||||||
if x.len().min(y.len()) == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
sys::ImPlot_PlotLinedoublePtrdoublePtr(
|
|
||||||
im_str!("{}", self.label).as_ptr() as *const i8,
|
|
||||||
x.as_ptr(),
|
|
||||||
y.as_ptr(),
|
|
||||||
x.len().min(y.len()) as i32, // "as" casts saturate as of Rust 1.45. This is safe here.
|
|
||||||
0, // No offset
|
|
||||||
std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Struct to provide functionality for creating a scatter plot
|
|
||||||
pub struct PlotScatter {
|
|
||||||
/// Label to show in the legend for this scatter plot
|
|
||||||
label: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotScatter {
|
|
||||||
/// Create a new scatter plot to be shown. Does not draw anything yet.
|
|
||||||
pub fn new(label: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
label: label.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw a previously-created scatter plot. Use this in closures passed to
|
|
||||||
/// [`Plot::build()`](struct.Plot.html#method.build)
|
|
||||||
pub fn plot(&self, x: &Vec<f64>, y: &Vec<f64>) {
|
|
||||||
// If there is no data to plot, we stop here
|
|
||||||
if x.len().min(y.len()) == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
sys::ImPlot_PlotScatterdoublePtrdoublePtr(
|
|
||||||
im_str!("{}", self.label).as_ptr() as *const i8,
|
|
||||||
x.as_ptr(),
|
|
||||||
y.as_ptr(),
|
|
||||||
x.len().min(y.len()) as i32, // "as" casts saturate as of Rust 1.45. This is safe here.
|
|
||||||
0, // No offset
|
|
||||||
std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Struct to provide bar plotting functionality.
|
|
||||||
pub struct PlotBars {
|
|
||||||
/// Label to show in the legend for this line
|
|
||||||
label: String,
|
|
||||||
|
|
||||||
/// Width of the bars, in plot coordinate terms
|
|
||||||
bar_width: f64,
|
|
||||||
|
|
||||||
/// Horizontal bar mode
|
|
||||||
horizontal_bars: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotBars {
|
|
||||||
/// Create a new bar plot to be shown. Defaults to drawing vertical bars.
|
|
||||||
/// Does not draw anything yet.
|
|
||||||
pub fn new(label: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
label: label.to_owned(),
|
|
||||||
bar_width: 0.67, // Default value taken from C++ implot
|
|
||||||
horizontal_bars: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the width of the bars
|
|
||||||
pub fn with_bar_width(mut self, bar_width: f64) -> Self {
|
|
||||||
self.bar_width = bar_width;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the bars to be horizontal (default is vertical)
|
|
||||||
pub fn with_horizontal_bars(mut self) -> Self {
|
|
||||||
self.horizontal_bars = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw a previously-created bar plot. Use this in closures passed to
|
|
||||||
/// [`Plot::build()`](struct.Plot.html#method.build). The `axis_positions`
|
|
||||||
/// specify where on the corersponding axis (X for vertical mode, Y for horizontal mode) the
|
|
||||||
/// bar is drawn, and the `bar_values` specify what values the bars have.
|
|
||||||
pub fn plot(&self, axis_positions: &Vec<f64>, bar_values: &Vec<f64>) {
|
|
||||||
let number_of_points = axis_positions.len().min(bar_values.len());
|
|
||||||
// If there is no data to plot, we stop here
|
|
||||||
if number_of_points == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
// C++ implot has separate functions for the two variants, but the interfaces
|
|
||||||
// are the same, so they are unified here. The x and y values have different
|
|
||||||
// meanings though, hence the swapping around before they are passed to the
|
|
||||||
// plotting function.
|
|
||||||
let (plot_function, x, y);
|
|
||||||
if self.horizontal_bars {
|
|
||||||
plot_function = sys::ImPlot_PlotBarsHdoublePtrdoublePtr
|
|
||||||
as unsafe extern "C" fn(*const i8, *const f64, *const f64, i32, f64, i32, i32);
|
|
||||||
x = bar_values;
|
|
||||||
y = axis_positions;
|
|
||||||
} else {
|
|
||||||
plot_function = sys::ImPlot_PlotBarsdoublePtrdoublePtr
|
|
||||||
as unsafe extern "C" fn(*const i8, *const f64, *const f64, i32, f64, i32, i32);
|
|
||||||
x = axis_positions;
|
|
||||||
y = bar_values;
|
|
||||||
};
|
|
||||||
|
|
||||||
plot_function(
|
|
||||||
im_str!("{}", self.label).as_ptr() as *const i8,
|
|
||||||
x.as_ptr(),
|
|
||||||
y.as_ptr(),
|
|
||||||
number_of_points as i32, // "as" casts saturate as of Rust 1.45. This is safe here.
|
|
||||||
self.bar_width,
|
|
||||||
0, // No offset
|
|
||||||
std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Struct to provide functionality for adding text within a plot
|
|
||||||
pub struct PlotText {
|
|
||||||
/// Label to show in plot
|
|
||||||
label: String,
|
|
||||||
|
|
||||||
/// X component of the pixel offset to be used. Will be used independently of the actual plot
|
|
||||||
/// scaling. Defaults to 0.
|
|
||||||
pixel_offset_x: f32,
|
|
||||||
|
|
||||||
/// Y component of the pixel offset to be used. Will be used independently of the actual plot
|
|
||||||
/// scaling. Defaults to 0.
|
|
||||||
pixel_offset_y: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotText {
|
|
||||||
/// Create a new text label to be shown. Does not draw anything yet.
|
|
||||||
pub fn new(label: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
label: label.into(),
|
|
||||||
pixel_offset_x: 0.0,
|
|
||||||
pixel_offset_y: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a pixel offset to the text to be plotted. This offset will be independent of the
|
|
||||||
/// scaling of the plot itself.
|
|
||||||
pub fn with_pixel_offset(mut self, offset_x: f32, offset_y: f32) -> Self {
|
|
||||||
self.pixel_offset_x = offset_x;
|
|
||||||
self.pixel_offset_y = offset_y;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw the text label in the plot at the given position, optionally vertically. Use this in
|
|
||||||
/// closures passed to [`Plot::build()`](struct.Plot.html#method.build)
|
|
||||||
pub fn plot(&self, x: f64, y: f64, vertical: bool) {
|
|
||||||
// If there is nothing to show, don't do anything
|
|
||||||
if self.label == "" {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
sys::ImPlot_PlotTextdouble(
|
|
||||||
im_str!("{}", self.label).as_ptr() as *const i8,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
vertical,
|
|
||||||
sys::ImVec2 {
|
|
||||||
x: self.pixel_offset_x,
|
|
||||||
y: self.pixel_offset_y,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Color maps -----------------------------------------------------------------------------
|
// --- Color maps -----------------------------------------------------------------------------
|
||||||
/// Switch to one of the built-in preset colormaps. If samples is greater than 1, the map will be
|
/// Switch to one of the built-in preset colormaps. If samples is greater than 1, the map will be
|
||||||
|
|
448
src/plot.rs
Normal file
448
src/plot.rs
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
//! # Plot module
|
||||||
|
//!
|
||||||
|
//! This module defines the `Plot` struct, which is used to create a 2D plot that will
|
||||||
|
//! contain all other objects that can be created using this library.
|
||||||
|
extern crate implot_sys as sys;
|
||||||
|
use bitflags::bitflags;
|
||||||
|
pub use sys::imgui::Condition;
|
||||||
|
use sys::imgui::{im_str, ImString};
|
||||||
|
pub use sys::{ImPlotLimits, ImPlotPoint, ImPlotRange, ImVec2, ImVec4};
|
||||||
|
|
||||||
|
const DEFAULT_PLOT_SIZE_X: f32 = 400.0;
|
||||||
|
const DEFAULT_PLOT_SIZE_Y: f32 = 400.0;
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Flags for customizing plot behavior and interaction. Documentation copied from implot.h for
|
||||||
|
/// convenience. ImPlot itself also has a "CanvasOnly" flag, which can be emulated here with
|
||||||
|
/// the combination of `NO_LEGEND`, `NO_MENUS`, `NO_BOX_SELECT` and `NO_MOUSE_POSITION`.
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct PlotFlags: u32 {
|
||||||
|
/// "Default" according to original docs
|
||||||
|
const NONE = sys::ImPlotFlags__ImPlotFlags_None;
|
||||||
|
/// Plot items will not be highlighted when their legend entry is hovered
|
||||||
|
const NO_LEGEND = sys::ImPlotFlags__ImPlotFlags_NoLegend;
|
||||||
|
/// The user will not be able to open context menus with double-right click
|
||||||
|
const NO_MENUS = sys::ImPlotFlags__ImPlotFlags_NoMenus;
|
||||||
|
/// The user will not be able to box-select with right-mouse
|
||||||
|
const NO_BOX_SELECT = sys::ImPlotFlags__ImPlotFlags_NoBoxSelect;
|
||||||
|
/// The mouse position, in plot coordinates, will not be displayed
|
||||||
|
const NO_MOUSE_POSITION = sys::ImPlotFlags__ImPlotFlags_NoMousePos;
|
||||||
|
/// Plot items will not be highlighted when their legend entry is hovered
|
||||||
|
const NO_HIGHLIGHT = sys::ImPlotFlags__ImPlotFlags_NoHighlight;
|
||||||
|
/// A child window region will not be used to capture mouse scroll (can boost performance
|
||||||
|
/// for single ImGui window applications)
|
||||||
|
const NO_CHILD = sys::ImPlotFlags__ImPlotFlags_NoChild;
|
||||||
|
/// Enable a 2nd y axis
|
||||||
|
const Y_AXIS_2 = sys::ImPlotFlags__ImPlotFlags_YAxis2;
|
||||||
|
/// Enable a 3nd y axis
|
||||||
|
const Y_AXIS_3 = sys::ImPlotFlags__ImPlotFlags_YAxis3;
|
||||||
|
/// The user will be able to draw query rects with middle-mouse
|
||||||
|
const QUERY = sys::ImPlotFlags__ImPlotFlags_Query;
|
||||||
|
/// The default mouse cursor will be replaced with a crosshair when hovered
|
||||||
|
const CROSSHAIRS = sys::ImPlotFlags__ImPlotFlags_Crosshairs;
|
||||||
|
/// Plot data outside the plot area will be culled from rendering
|
||||||
|
const ANTIALIASED = sys::ImPlotFlags__ImPlotFlags_AntiAliased;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Axis flags. Documentation copied from implot.h for convenience. ImPlot itself also
|
||||||
|
/// has `Lock`, which combines `LOCK_MIN` and `LOCK_MAX`, and `NoDecorations`, which combines
|
||||||
|
/// `NO_GRID_LINES`, `NO_TICK_MARKS` and `NO_TICK_LABELS`.
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct AxisFlags: u32 {
|
||||||
|
/// "Default" according to original docs
|
||||||
|
const NONE = sys::ImPlotAxisFlags__ImPlotAxisFlags_None;
|
||||||
|
/// Grid lines will not be displayed
|
||||||
|
const NO_GRID_LINES = sys::ImPlotAxisFlags__ImPlotAxisFlags_NoGridLines;
|
||||||
|
/// Tick marks will not be displayed
|
||||||
|
const NO_TICK_MARKS = sys::ImPlotAxisFlags__ImPlotAxisFlags_NoTickMarks;
|
||||||
|
/// Text labels will not be displayed
|
||||||
|
const NO_TICK_LABELS = sys::ImPlotAxisFlags__ImPlotAxisFlags_NoTickLabels;
|
||||||
|
/// A logartithmic (base 10) axis scale will be used (mutually exclusive with AxisFlags::TIME)
|
||||||
|
const LOG_SCALE = sys::ImPlotAxisFlags__ImPlotAxisFlags_LogScale;
|
||||||
|
/// Axis will display date/time formatted labels (mutually exclusive with AxisFlags::LOG_SCALE)
|
||||||
|
const TIME = sys::ImPlotAxisFlags__ImPlotAxisFlags_Time;
|
||||||
|
/// The axis will be inverted
|
||||||
|
const INVERT = sys::ImPlotAxisFlags__ImPlotAxisFlags_Invert;
|
||||||
|
/// The axis minimum value will be locked when panning/zooming
|
||||||
|
const LOCK_MIN = sys::ImPlotAxisFlags__ImPlotAxisFlags_LockMin;
|
||||||
|
/// The axis maximum value will be locked when panning/zooming
|
||||||
|
const LOCK_MAX = sys::ImPlotAxisFlags__ImPlotAxisFlags_LockMax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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:
|
||||||
|
/// ```no_run
|
||||||
|
/// # use implot;
|
||||||
|
/// implot::Plot::new("my title")
|
||||||
|
/// .size(300.0, 200.0) // other things such as .x_label("some_label") can be added too
|
||||||
|
/// .build( || {
|
||||||
|
/// // Do things such as plotting lines
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// (If you are coming from the C++ implementation or the C bindings: build() calls both
|
||||||
|
/// begin() and end() internally)
|
||||||
|
pub struct Plot {
|
||||||
|
/// Title of the plot, shown on top.
|
||||||
|
title: String,
|
||||||
|
/// Size of the plot in x direction, in the same units imgui uses.
|
||||||
|
size_x: f32,
|
||||||
|
/// Size of the plot in y direction, in the same units imgui uses.
|
||||||
|
size_y: f32,
|
||||||
|
/// Label of the x axis, shown on the bottom
|
||||||
|
x_label: String,
|
||||||
|
/// Label of the y axis, shown on the left
|
||||||
|
y_label: String,
|
||||||
|
/// X axis limits, if present
|
||||||
|
x_limits: Option<ImPlotRange>,
|
||||||
|
/// Y axis limits, if present
|
||||||
|
y_limits: Option<ImPlotRange>,
|
||||||
|
/// Condition on which the x limits are set
|
||||||
|
x_limit_condition: Option<Condition>,
|
||||||
|
/// Condition on which the y limits are set (first y axis for now)
|
||||||
|
y_limit_condition: Option<Condition>,
|
||||||
|
/// Positions for custom X axis ticks, if any
|
||||||
|
x_tick_positions: Option<Vec<f64>>,
|
||||||
|
/// Labels for custom X 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.
|
||||||
|
x_tick_labels: Option<Vec<ImString>>,
|
||||||
|
/// 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<Vec<f64>>,
|
||||||
|
/// 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<Vec<ImString>>,
|
||||||
|
/// Whether to also show the default Y ticks when showing custom ticks or not
|
||||||
|
show_y_default_ticks: bool,
|
||||||
|
/// 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
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plot {
|
||||||
|
/// Create a new plot with some defaults set. Does not draw anything yet.
|
||||||
|
pub fn new(title: &str) -> Self {
|
||||||
|
// TODO(4bb4) question these defaults, maybe remove some of them
|
||||||
|
Self {
|
||||||
|
title: title.to_owned(),
|
||||||
|
size_x: DEFAULT_PLOT_SIZE_X,
|
||||||
|
size_y: DEFAULT_PLOT_SIZE_Y,
|
||||||
|
x_label: "".to_owned(),
|
||||||
|
y_label: "".to_owned(),
|
||||||
|
x_limits: None,
|
||||||
|
y_limits: None,
|
||||||
|
x_limit_condition: None,
|
||||||
|
y_limit_condition: None,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the plot size, given as [size_x, size_y]. Units are the same as
|
||||||
|
/// what imgui uses. TODO(4b4) ... which is? I'm not sure it's pixels
|
||||||
|
#[inline]
|
||||||
|
pub fn size(mut self, size_x: f32, size_y: f32) -> Self {
|
||||||
|
self.size_x = size_x;
|
||||||
|
self.size_y = size_y;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the x label of the plot
|
||||||
|
#[inline]
|
||||||
|
pub fn x_label(mut self, label: &str) -> Self {
|
||||||
|
self.x_label = label.to_owned();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the y label of the plot
|
||||||
|
#[inline]
|
||||||
|
pub fn y_label(mut self, label: &str) -> Self {
|
||||||
|
self.y_label = label.to_owned();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the x limits of the plot
|
||||||
|
#[inline]
|
||||||
|
pub fn x_limits(mut self, limits: &ImPlotRange, condition: Condition) -> Self {
|
||||||
|
self.x_limits = Some(*limits);
|
||||||
|
self.x_limit_condition = Some(condition);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the y limits of the plot
|
||||||
|
#[inline]
|
||||||
|
pub fn y_limits(mut self, limits: &ImPlotRange, condition: Condition) -> Self {
|
||||||
|
self.y_limits = Some(*limits);
|
||||||
|
self.y_limit_condition = Some(condition);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// determines whether the default ticks are also shown.
|
||||||
|
#[inline]
|
||||||
|
pub fn x_ticks(mut self, ticks: &Vec<f64>, show_default: bool) -> Self {
|
||||||
|
self.x_tick_positions = Some(ticks.clone());
|
||||||
|
self.show_x_default_ticks = show_default;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// determines whether the default ticks are also shown.
|
||||||
|
#[inline]
|
||||||
|
pub fn y_ticks(mut self, ticks: &Vec<f64>, show_default: bool) -> Self {
|
||||||
|
self.y_tick_positions = Some(ticks.clone());
|
||||||
|
self.show_y_default_ticks = show_default;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set X ticks with labels for the plot. The vector contains one position and label
|
||||||
|
/// each in the form of a tuple `(label_position, label_string)`. The `show_default`
|
||||||
|
/// setting determines whether the default ticks are also shown.
|
||||||
|
#[inline]
|
||||||
|
pub fn x_ticks_with_labels(
|
||||||
|
mut self,
|
||||||
|
tick_labels: &Vec<(f64, String)>,
|
||||||
|
show_default: bool,
|
||||||
|
) -> Self {
|
||||||
|
self.x_tick_positions = Some(tick_labels.iter().map(|x| x.0).collect());
|
||||||
|
self.x_tick_labels = Some(tick_labels.iter().map(|x| im_str!("{}", x.1)).collect());
|
||||||
|
self.show_x_default_ticks = show_default;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set Y ticks with labels for the plot. The vector contains one position and label
|
||||||
|
/// each in 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_with_labels(
|
||||||
|
mut self,
|
||||||
|
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;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the plot flags, see the help for `PlotFlags` for what the available flags are
|
||||||
|
#[inline]
|
||||||
|
pub fn with_plot_flags(mut self, flags: &PlotFlags) -> Self {
|
||||||
|
self.plot_flags = flags.bits() as sys::ImPlotFlags;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the axis flags for the X axis in this plot
|
||||||
|
#[inline]
|
||||||
|
pub fn with_x_axis_flags(mut self, flags: &AxisFlags) -> Self {
|
||||||
|
self.x_flags = flags.bits() as sys::ImPlotAxisFlags;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the axis flags for the first 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;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal helper function to set axis limits in case they are specified.
|
||||||
|
fn maybe_set_axis_limits(&self) {
|
||||||
|
// Set X limits if specified
|
||||||
|
if let (Some(limits), Some(condition)) = (self.x_limits, self.x_limit_condition) {
|
||||||
|
unsafe {
|
||||||
|
sys::ImPlot_SetNextPlotLimitsX(limits.Min, limits.Max, condition as sys::ImGuiCond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal helper function to set tick labels in case they are specified. This does the
|
||||||
|
/// preparation work that is the same for both the X and Y axis plots, then calls the
|
||||||
|
/// "set next plot ticks" wrapper functions for both X and Y.
|
||||||
|
fn maybe_set_tick_labels(&self) {
|
||||||
|
// Show x ticks if they are available
|
||||||
|
if self.x_tick_positions.is_some() && self.x_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.x_tick_labels {
|
||||||
|
pointer_vec = labels_value
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.as_ptr() as *const i8)
|
||||||
|
.collect::<Vec<*const i8>>();
|
||||||
|
pointer_vec.as_mut_ptr()
|
||||||
|
} else {
|
||||||
|
std::ptr::null_mut()
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
sys::ImPlot_SetNextPlotTicksXdoublePtr(
|
||||||
|
self.x_tick_positions.as_ref().unwrap().as_ptr(),
|
||||||
|
self.x_tick_positions.as_ref().unwrap().len() as i32,
|
||||||
|
labels_pointer,
|
||||||
|
self.show_x_default_ticks,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Vec<*const i8>>();
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to show the plot. If this returns a token, the plot will actually
|
||||||
|
/// be drawn. In this case, use the drawing functionality to draw things on the
|
||||||
|
/// plot, and then call `end()` on the token when done with the plot.
|
||||||
|
/// If none was returned, that means the plot is not rendered.
|
||||||
|
///
|
||||||
|
/// For a convenient implementation of all this, use [`build()`](struct.Plot.html#method.build)
|
||||||
|
/// instead.
|
||||||
|
pub fn begin(&self) -> Option<PlotToken> {
|
||||||
|
self.maybe_set_axis_limits();
|
||||||
|
self.maybe_set_tick_labels();
|
||||||
|
|
||||||
|
let should_render = unsafe {
|
||||||
|
sys::ImPlot_BeginPlot(
|
||||||
|
im_str!("{}", self.title).as_ptr(),
|
||||||
|
im_str!("{}", self.x_label).as_ptr(),
|
||||||
|
im_str!("{}", self.y_label).as_ptr(),
|
||||||
|
sys::ImVec2 {
|
||||||
|
x: self.size_x as f32,
|
||||||
|
y: self.size_y as f32,
|
||||||
|
},
|
||||||
|
self.plot_flags,
|
||||||
|
self.x_flags,
|
||||||
|
self.y_flags,
|
||||||
|
self.y2_flags,
|
||||||
|
self.y3_flags,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_render {
|
||||||
|
Some(PlotToken {
|
||||||
|
plot_title: self.title.clone(),
|
||||||
|
has_ended: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// In contrast with imgui windows, end() does not have to be
|
||||||
|
// called if we don't render. This is more like an imgui popup modal.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a window and runs a closure to construct the contents.
|
||||||
|
///
|
||||||
|
/// Note: the closure is not called if ImPlot::BeginPlot() returned
|
||||||
|
/// false - TODO(4bb4) figure out if this is if things are not rendered
|
||||||
|
pub fn build<F: FnOnce()>(self, f: F) {
|
||||||
|
if let Some(token) = self.begin() {
|
||||||
|
f();
|
||||||
|
token.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks a plot that must be ended by calling `.end()`
|
||||||
|
pub struct PlotToken {
|
||||||
|
/// For better error messages
|
||||||
|
plot_title: String,
|
||||||
|
/// Whether end() has been called on this already or not
|
||||||
|
has_ended: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlotToken {
|
||||||
|
/// End a previously begin()'ed plot.
|
||||||
|
pub fn end(mut self) {
|
||||||
|
self.has_ended = true;
|
||||||
|
unsafe { sys::ImPlot_EndPlot() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for PlotToken {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.has_ended && !std::thread::panicking() {
|
||||||
|
panic!(
|
||||||
|
"Warning: A PlotToken for plot \"{}\" was not called end() on",
|
||||||
|
self.plot_title
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
src/plot_elements.rs
Normal file
200
src/plot_elements.rs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
use sys::imgui::im_str;
|
||||||
|
|
||||||
|
// --- Actual plotting functionality -------------------------------------------------------------
|
||||||
|
/// Struct to provide functionality for plotting a line in a plot.
|
||||||
|
pub struct PlotLine {
|
||||||
|
/// Label to show in the legend for this line
|
||||||
|
label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlotLine {
|
||||||
|
/// Create a new line to be plotted. Does not draw anything yet.
|
||||||
|
pub fn new(label: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plot a line. Use this in closures passed to [`Plot::build()`](struct.Plot.html#method.build)
|
||||||
|
pub fn plot(&self, x: &Vec<f64>, y: &Vec<f64>) {
|
||||||
|
// If there is no data to plot, we stop here
|
||||||
|
if x.len().min(y.len()) == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
sys::ImPlot_PlotLinedoublePtrdoublePtr(
|
||||||
|
im_str!("{}", self.label).as_ptr() as *const i8,
|
||||||
|
x.as_ptr(),
|
||||||
|
y.as_ptr(),
|
||||||
|
x.len().min(y.len()) as i32, // "as" casts saturate as of Rust 1.45. This is safe here.
|
||||||
|
0, // No offset
|
||||||
|
std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct to provide functionality for creating a scatter plot
|
||||||
|
pub struct PlotScatter {
|
||||||
|
/// Label to show in the legend for this scatter plot
|
||||||
|
label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlotScatter {
|
||||||
|
/// Create a new scatter plot to be shown. Does not draw anything yet.
|
||||||
|
pub fn new(label: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a previously-created scatter plot. Use this in closures passed to
|
||||||
|
/// [`Plot::build()`](struct.Plot.html#method.build)
|
||||||
|
pub fn plot(&self, x: &Vec<f64>, y: &Vec<f64>) {
|
||||||
|
// If there is no data to plot, we stop here
|
||||||
|
if x.len().min(y.len()) == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
sys::ImPlot_PlotScatterdoublePtrdoublePtr(
|
||||||
|
im_str!("{}", self.label).as_ptr() as *const i8,
|
||||||
|
x.as_ptr(),
|
||||||
|
y.as_ptr(),
|
||||||
|
x.len().min(y.len()) as i32, // "as" casts saturate as of Rust 1.45. This is safe here.
|
||||||
|
0, // No offset
|
||||||
|
std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct to provide bar plotting functionality.
|
||||||
|
pub struct PlotBars {
|
||||||
|
/// Label to show in the legend for this line
|
||||||
|
label: String,
|
||||||
|
|
||||||
|
/// Width of the bars, in plot coordinate terms
|
||||||
|
bar_width: f64,
|
||||||
|
|
||||||
|
/// Horizontal bar mode
|
||||||
|
horizontal_bars: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlotBars {
|
||||||
|
/// Create a new bar plot to be shown. Defaults to drawing vertical bars.
|
||||||
|
/// Does not draw anything yet.
|
||||||
|
pub fn new(label: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.to_owned(),
|
||||||
|
bar_width: 0.67, // Default value taken from C++ implot
|
||||||
|
horizontal_bars: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the width of the bars
|
||||||
|
pub fn with_bar_width(mut self, bar_width: f64) -> Self {
|
||||||
|
self.bar_width = bar_width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the bars to be horizontal (default is vertical)
|
||||||
|
pub fn with_horizontal_bars(mut self) -> Self {
|
||||||
|
self.horizontal_bars = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a previously-created bar plot. Use this in closures passed to
|
||||||
|
/// [`Plot::build()`](struct.Plot.html#method.build). The `axis_positions`
|
||||||
|
/// specify where on the corersponding axis (X for vertical mode, Y for horizontal mode) the
|
||||||
|
/// bar is drawn, and the `bar_values` specify what values the bars have.
|
||||||
|
pub fn plot(&self, axis_positions: &Vec<f64>, bar_values: &Vec<f64>) {
|
||||||
|
let number_of_points = axis_positions.len().min(bar_values.len());
|
||||||
|
// If there is no data to plot, we stop here
|
||||||
|
if number_of_points == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
// C++ implot has separate functions for the two variants, but the interfaces
|
||||||
|
// are the same, so they are unified here. The x and y values have different
|
||||||
|
// meanings though, hence the swapping around before they are passed to the
|
||||||
|
// plotting function.
|
||||||
|
let (plot_function, x, y);
|
||||||
|
if self.horizontal_bars {
|
||||||
|
plot_function = sys::ImPlot_PlotBarsHdoublePtrdoublePtr
|
||||||
|
as unsafe extern "C" fn(*const i8, *const f64, *const f64, i32, f64, i32, i32);
|
||||||
|
x = bar_values;
|
||||||
|
y = axis_positions;
|
||||||
|
} else {
|
||||||
|
plot_function = sys::ImPlot_PlotBarsdoublePtrdoublePtr
|
||||||
|
as unsafe extern "C" fn(*const i8, *const f64, *const f64, i32, f64, i32, i32);
|
||||||
|
x = axis_positions;
|
||||||
|
y = bar_values;
|
||||||
|
};
|
||||||
|
|
||||||
|
plot_function(
|
||||||
|
im_str!("{}", self.label).as_ptr() as *const i8,
|
||||||
|
x.as_ptr(),
|
||||||
|
y.as_ptr(),
|
||||||
|
number_of_points as i32, // "as" casts saturate as of Rust 1.45. This is safe here.
|
||||||
|
self.bar_width,
|
||||||
|
0, // No offset
|
||||||
|
std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct to provide functionality for adding text within a plot
|
||||||
|
pub struct PlotText {
|
||||||
|
/// Label to show in plot
|
||||||
|
label: String,
|
||||||
|
|
||||||
|
/// X component of the pixel offset to be used. Will be used independently of the actual plot
|
||||||
|
/// scaling. Defaults to 0.
|
||||||
|
pixel_offset_x: f32,
|
||||||
|
|
||||||
|
/// Y component of the pixel offset to be used. Will be used independently of the actual plot
|
||||||
|
/// scaling. Defaults to 0.
|
||||||
|
pixel_offset_y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlotText {
|
||||||
|
/// Create a new text label to be shown. Does not draw anything yet.
|
||||||
|
pub fn new(label: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
pixel_offset_x: 0.0,
|
||||||
|
pixel_offset_y: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a pixel offset to the text to be plotted. This offset will be independent of the
|
||||||
|
/// scaling of the plot itself.
|
||||||
|
pub fn with_pixel_offset(mut self, offset_x: f32, offset_y: f32) -> Self {
|
||||||
|
self.pixel_offset_x = offset_x;
|
||||||
|
self.pixel_offset_y = offset_y;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the text label in the plot at the given position, optionally vertically. Use this in
|
||||||
|
/// closures passed to [`Plot::build()`](struct.Plot.html#method.build)
|
||||||
|
pub fn plot(&self, x: f64, y: f64, vertical: bool) {
|
||||||
|
// If there is nothing to show, don't do anything
|
||||||
|
if self.label == "" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
sys::ImPlot_PlotTextdouble(
|
||||||
|
im_str!("{}", self.label).as_ptr() as *const i8,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
vertical,
|
||||||
|
sys::ImVec2 {
|
||||||
|
x: self.pixel_offset_x,
|
||||||
|
y: self.pixel_offset_y,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue