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