1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
//! # Plot elements module //! //! This module defines the various structs that can be used for drawing different things such //! as lines, bars, scatter plots and text in a plot. For the module to create plots themselves, //! see `plot`. use crate::sys; use imgui::{im_str, ImString}; pub use crate::sys::ImPlotPoint; // --- 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: &[f64], y: &[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 plotting a line in a plot with stairs style. pub struct PlotStairs { /// Label to show in the legend for this line label: String, } impl PlotStairs { /// 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 stairs style line. Use this in closures passed to /// [`Plot::build()`](struct.Plot.html#method.build) pub fn plot(&self, x: &[f64], y: &[f64]) { // If there is no data to plot, we stop here if x.len().min(y.len()) == 0 { return; } unsafe { sys::ImPlot_PlotStairsdoublePtrdoublePtr( 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: &[f64], y: &[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 corresponding 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: &[f64], bar_values: &[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_PlotText( im_str!("{}", self.label).as_ptr() as *const i8, x, y, vertical, sys::ImVec2 { x: self.pixel_offset_x, y: self.pixel_offset_y, }, ); } } } /// Struct to provide functionality for creating headmaps. pub struct PlotHeatmap { /// Label to show in plot label: String, /// Scale range of the values shown. If this is set to `None`, the scale /// is computed based on the values given to the `plot` function. If there /// is a value, the tuple is interpreted as `(minimum, maximum)`. scale_range: Option<(f64, f64)>, /// Label C style format string, this is shown when a a value point is hovered. /// None means don't show a label. The label is stored directly as an ImString because /// that is what's needed for the plot call anyway. Conversion is done in the setter. label_format: Option<ImString>, /// Lower left point for the bounding rectangle. This is called `bounds_min` in the C++ code. drawarea_lower_left: ImPlotPoint, /// Upper right point for the bounding rectangle. This is called `bounds_max` in the C++ code. drawarea_upper_right: ImPlotPoint, } impl PlotHeatmap { /// Create a new heatmap to be shown. Uses the same defaults as the C++ version (see code for /// what those are), aside from the `scale_min` and `scale_max` values, which default to /// `None`, which is interpreted as "automatically make the scale fit the data". Does not draw /// anything yet. pub fn new(label: &str) -> Self { Self { label: label.to_owned(), scale_range: None, label_format: Some(im_str!("%.1f").to_owned()), drawarea_lower_left: ImPlotPoint { x: 0.0, y: 0.0 }, drawarea_upper_right: ImPlotPoint { x: 1.0, y: 1.0 }, } } /// Specify the scale for the shown colors by minimum and maximum value. pub fn with_scale(mut self, scale_min: f64, scale_max: f64) -> Self { self.scale_range = Some((scale_min, scale_max)); self } /// Specify the label format for hovered data points.. `None` means no label is shown. pub fn with_label_format(mut self, label_format: Option<&str>) -> Self { self.label_format = label_format.map(|x| im_str!("{}", x)); self } /// Specify the drawing area as the lower left and upper right point pub fn with_drawing_area(mut self, lower_left: ImPlotPoint, upper_right: ImPlotPoint) -> Self { self.drawarea_lower_left = lower_left; self.drawarea_upper_right = upper_right; self } /// Plot the heatmap, with the given values (assumed to be in row-major order), /// number of rows and number of columns. pub fn plot(&self, values: &[f64], number_of_rows: u32, number_of_cols: u32) { // If no range was given, determine that range let scale_range = self.scale_range.unwrap_or_else(|| { let mut min_seen = values[0]; let mut max_seen = values[0]; values.iter().for_each(|value| { min_seen = min_seen.min(*value); max_seen = max_seen.max(*value); }); (min_seen, max_seen) }); unsafe { sys::ImPlot_PlotHeatmapdoublePtr( im_str!("{}", self.label).as_ptr() as *const i8, values.as_ptr(), number_of_rows as i32, // Not sure why C++ code uses a signed value here number_of_cols as i32, // Not sure why C++ code uses a signed value here scale_range.0, scale_range.1, // "no label" is taken as null pointer in the C++ code, but we're using // option types in the Rust bindings because they are more idiomatic. if self.label_format.is_some() { self.label_format.as_ref().unwrap().as_ptr() as *const i8 } else { std::ptr::null() }, self.drawarea_lower_left, self.drawarea_upper_right, ); } } } /// Struct to provide stem plotting functionality. pub struct PlotStems { /// Label to show in the legend for this line label: String, /// Reference value for the y value, which the stems are "with respect to" reference_y: f64, } impl PlotStems { /// Create a new stem plot to be shown. Does not draw anything by itself, call /// [`PlotStems::plot`] on the struct for that. pub fn new(label: &str) -> Self { Self { label: label.to_owned(), reference_y: 0.0, // Default value taken from C++ implot } } /// Set the reference y value for the stems pub fn with_reference_y(mut self, reference_y: f64) -> Self { self.reference_y = reference_y; self } /// Draw a previously-created stem plot. Use this in closures passed to /// [`Plot::build()`](struct.Plot.html#method.build). The `axis_positions` specify where on the /// X axis the stems are drawn, and the `stem_values` specify what values the stems have. pub fn plot(&self, axis_positions: &[f64], stem_values: &[f64]) { let number_of_points = axis_positions.len().min(stem_values.len()); // If there is no data to plot, we stop here if number_of_points == 0 { return; } unsafe { sys::ImPlot_PlotStemsdoublePtrdoublePtr( im_str!("{}", self.label).as_ptr() as *const i8, axis_positions.as_ptr(), stem_values.as_ptr(), number_of_points as i32, // "as" casts saturate as of Rust 1.45. This is safe here. self.reference_y, 0, // No offset std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case ); } } }