Skip to main content

dear_implot/plots/
mod.rs

1//! Modular plot types for ImPlot
2//!
3//! This module provides a modular approach to different plot types,
4//! each with their own builder pattern and configuration options.
5
6pub mod bar;
7pub mod bar_groups;
8pub mod digital;
9pub mod dummy;
10pub mod error_bars;
11pub mod heatmap;
12pub mod histogram;
13pub mod image;
14pub mod inf_lines;
15pub mod line;
16pub mod pie;
17pub mod polygon;
18pub mod scatter;
19pub mod shaded;
20pub mod stairs;
21pub mod stems;
22pub mod text;
23
24use crate::sys;
25use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_slice, with_scratch_txt_slice_with_opt};
26use std::cell::RefCell;
27use std::os::raw::c_char;
28
29// Re-export all plot types for convenience
30pub use bar::*;
31pub use bar_groups::*;
32pub use digital::*;
33pub use dummy::*;
34pub use error_bars::*;
35pub use heatmap::*;
36pub use histogram::*;
37pub use image::*;
38pub use inf_lines::*;
39pub use line::*;
40pub use pie::*;
41pub use polygon::*;
42pub use scatter::*;
43pub use shaded::*;
44pub use stairs::*;
45pub use stems::*;
46pub use text::*;
47
48thread_local! {
49    static NEXT_PLOT_SPEC: RefCell<Option<sys::ImPlotSpec_c>> = RefCell::new(None);
50}
51
52fn color4(rgba: [f32; 4]) -> sys::ImVec4_c {
53    sys::ImVec4_c {
54        x: rgba[0],
55        y: rgba[1],
56        z: rgba[2],
57        w: rgba[3],
58    }
59}
60
61/// Common style overrides for plot items backed by `ImPlotSpec`.
62///
63/// This provides a stable high-level Rust entry point for ImPlot v0.18 item
64/// styling without exposing raw FFI structs at every call site.
65#[derive(Debug, Clone, Copy, Default, PartialEq)]
66pub struct PlotItemStyle {
67    pub(crate) line_color: Option<sys::ImVec4_c>,
68    pub(crate) line_weight: Option<f32>,
69    pub(crate) fill_color: Option<sys::ImVec4_c>,
70    pub(crate) fill_alpha: Option<f32>,
71    pub(crate) marker: Option<sys::ImPlotMarker>,
72    pub(crate) marker_size: Option<f32>,
73    pub(crate) marker_line_color: Option<sys::ImVec4_c>,
74    pub(crate) marker_fill_color: Option<sys::ImVec4_c>,
75    pub(crate) size: Option<f32>,
76}
77
78impl PlotItemStyle {
79    /// Create an empty style override that keeps ImPlot defaults.
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    /// Override the plot item's line color. Use the alpha channel for line transparency.
85    pub fn with_line_color(mut self, color: [f32; 4]) -> Self {
86        self.line_color = Some(color4(color));
87        self
88    }
89
90    /// Override the plot item's line width in pixels.
91    pub fn with_line_weight(mut self, weight: f32) -> Self {
92        self.line_weight = Some(weight);
93        self
94    }
95
96    /// Override the plot item's fill color.
97    pub fn with_fill_color(mut self, color: [f32; 4]) -> Self {
98        self.fill_color = Some(color4(color));
99        self
100    }
101
102    /// Override the fill alpha multiplier used by filled regions and marker faces.
103    pub fn with_fill_alpha(mut self, alpha: f32) -> Self {
104        self.fill_alpha = Some(alpha);
105        self
106    }
107
108    /// Override the marker type.
109    pub fn with_marker(mut self, marker: crate::Marker) -> Self {
110        self.marker = Some(marker as sys::ImPlotMarker);
111        self
112    }
113
114    /// Override the marker size in pixels.
115    pub fn with_marker_size(mut self, size: f32) -> Self {
116        self.marker_size = Some(size);
117        self
118    }
119
120    /// Override the marker outline color.
121    pub fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
122        self.marker_line_color = Some(color4(color));
123        self
124    }
125
126    /// Override the marker fill color.
127    pub fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
128        self.marker_fill_color = Some(color4(color));
129        self
130    }
131
132    /// Override the generic size field used by some item types such as error bars.
133    pub fn with_size(mut self, size: f32) -> Self {
134        self.size = Some(size);
135        self
136    }
137
138    pub(crate) fn apply_to_spec(self, spec: &mut sys::ImPlotSpec_c) {
139        if let Some(line_color) = self.line_color {
140            spec.LineColor = line_color;
141        }
142        if let Some(line_weight) = self.line_weight {
143            spec.LineWeight = line_weight;
144        }
145        if let Some(fill_color) = self.fill_color {
146            spec.FillColor = fill_color;
147        }
148        if let Some(fill_alpha) = self.fill_alpha {
149            spec.FillAlpha = fill_alpha;
150        }
151        if let Some(marker) = self.marker {
152            spec.Marker = marker;
153        }
154        if let Some(marker_size) = self.marker_size {
155            spec.MarkerSize = marker_size;
156        }
157        if let Some(marker_line_color) = self.marker_line_color {
158            spec.MarkerLineColor = marker_line_color;
159        }
160        if let Some(marker_fill_color) = self.marker_fill_color {
161            spec.MarkerFillColor = marker_fill_color;
162        }
163        if let Some(size) = self.size {
164            spec.Size = size;
165        }
166    }
167}
168
169/// Shared ImPlot item-style builder methods for plot builders backed by `ImPlotSpec`.
170///
171/// Importing `dear_implot::*` brings this trait into scope, so every supported
172/// plot builder exposes the same styling methods.
173pub trait PlotItemStyled: Sized {
174    fn style_mut(&mut self) -> &mut PlotItemStyle;
175
176    /// Replace the entire item style override for this plot.
177    fn with_style(mut self, style: PlotItemStyle) -> Self {
178        *self.style_mut() = style;
179        self
180    }
181
182    /// Set the line color. Use the alpha channel to control line transparency.
183    fn with_line_color(mut self, color: [f32; 4]) -> Self {
184        self.style_mut().line_color = Some(color4(color));
185        self
186    }
187
188    /// Set the line width in pixels.
189    fn with_line_weight(mut self, weight: f32) -> Self {
190        self.style_mut().line_weight = Some(weight);
191        self
192    }
193
194    /// Set the fill color.
195    fn with_fill_color(mut self, color: [f32; 4]) -> Self {
196        self.style_mut().fill_color = Some(color4(color));
197        self
198    }
199
200    /// Set the fill alpha multiplier used for fills and marker faces.
201    fn with_fill_alpha(mut self, alpha: f32) -> Self {
202        self.style_mut().fill_alpha = Some(alpha);
203        self
204    }
205
206    /// Set the marker type.
207    fn with_marker(mut self, marker: crate::Marker) -> Self {
208        self.style_mut().marker = Some(marker as sys::ImPlotMarker);
209        self
210    }
211
212    /// Set the marker size in pixels.
213    fn with_marker_size(mut self, size: f32) -> Self {
214        self.style_mut().marker_size = Some(size);
215        self
216    }
217
218    /// Set the marker outline color.
219    fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
220        self.style_mut().marker_line_color = Some(color4(color));
221        self
222    }
223
224    /// Set the marker fill color.
225    fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
226        self.style_mut().marker_fill_color = Some(color4(color));
227        self
228    }
229
230    /// Set the generic size field used by some plot types such as error bars and digital plots.
231    fn with_size(mut self, size: f32) -> Self {
232        self.style_mut().size = Some(size);
233        self
234    }
235}
236
237/// Common trait for all plot types
238pub trait Plot {
239    /// Plot this element
240    fn plot(&self);
241
242    /// Get the label for this plot
243    fn label(&self) -> &str;
244}
245
246/// Common trait for plot data validation
247pub trait PlotData {
248    /// Get the label for this plot
249    fn label(&self) -> &str;
250
251    /// Get the length of the data
252    fn data_len(&self) -> usize;
253
254    /// Check if the data is empty
255    fn is_empty(&self) -> bool {
256        self.data_len() == 0
257    }
258
259    /// Validate the data for plotting
260    fn validate(&self) -> Result<(), PlotError> {
261        if self.is_empty() {
262            Err(PlotError::EmptyData)
263        } else {
264            Ok(())
265        }
266    }
267}
268
269/// Errors that can occur during plotting
270#[derive(Debug, Clone, PartialEq)]
271pub enum PlotError {
272    /// Data arrays have mismatched lengths
273    DataLengthMismatch { x_len: usize, y_len: usize },
274    /// Data is empty
275    EmptyData,
276    /// Invalid parameter value or data
277    InvalidData(String),
278    /// String conversion error
279    StringConversion(String),
280    /// Plot creation failed
281    PlotCreationFailed(String),
282}
283
284impl std::fmt::Display for PlotError {
285    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        match self {
287            PlotError::DataLengthMismatch { x_len, y_len } => {
288                write!(
289                    f,
290                    "Data length mismatch: x has {} elements, y has {} elements",
291                    x_len, y_len
292                )
293            }
294            PlotError::EmptyData => write!(f, "Data is empty"),
295            PlotError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
296            PlotError::StringConversion(msg) => write!(f, "String conversion error: {}", msg),
297            PlotError::PlotCreationFailed(msg) => write!(f, "Plot creation failed: {}", msg),
298        }
299    }
300}
301
302impl std::error::Error for PlotError {}
303
304// Note: PlotData trait is implemented by individual plot types
305// rather than raw data types, since each plot needs its own label
306
307/// Helper function to validate data length consistency for slices
308pub fn validate_data_lengths<T, U>(data1: &[T], data2: &[U]) -> Result<(), PlotError> {
309    if data1.is_empty() || data2.is_empty() {
310        return Err(PlotError::EmptyData);
311    }
312
313    if data1.len() != data2.len() {
314        return Err(PlotError::DataLengthMismatch {
315            x_len: data1.len(),
316            y_len: data2.len(),
317        });
318    }
319
320    Ok(())
321}
322
323pub(crate) fn with_plot_str<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> Option<R> {
324    if s.contains('\0') {
325        None
326    } else {
327        Some(with_scratch_txt(s, f))
328    }
329}
330
331pub(crate) fn with_plot_str_or_empty<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> R {
332    let s = if s.contains('\0') { "" } else { s };
333    with_scratch_txt(s, f)
334}
335
336pub(crate) fn with_plot_str_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
337    let cleaned: Vec<&str> = txts
338        .iter()
339        .map(|&s| if s.contains('\0') { "" } else { s })
340        .collect();
341    with_scratch_txt_slice(&cleaned, f)
342}
343
344pub(crate) fn with_plot_str_slice_with_opt<R>(
345    txts: &[&str],
346    txt_opt: Option<&str>,
347    f: impl FnOnce(&[*const c_char], *const c_char) -> R,
348) -> R {
349    let cleaned: Vec<&str> = txts
350        .iter()
351        .map(|&s| if s.contains('\0') { "" } else { s })
352        .collect();
353    let txt_opt = txt_opt.filter(|s| !s.contains('\0'));
354    with_scratch_txt_slice_with_opt(&cleaned, txt_opt, f)
355}
356
357pub(crate) fn default_plot_spec() -> sys::ImPlotSpec_c {
358    let auto_col = sys::ImVec4_c {
359        x: 0.0,
360        y: 0.0,
361        z: 0.0,
362        w: -1.0,
363    };
364
365    sys::ImPlotSpec_c {
366        LineColor: auto_col,
367        LineColors: std::ptr::null_mut(),
368        LineWeight: 1.0,
369        FillColor: auto_col,
370        FillColors: std::ptr::null_mut(),
371        FillAlpha: 1.0,
372        Marker: sys::ImPlotMarker_None as _,
373        MarkerSize: 4.0,
374        MarkerSizes: std::ptr::null_mut(),
375        MarkerLineColor: auto_col,
376        MarkerLineColors: std::ptr::null_mut(),
377        MarkerFillColor: auto_col,
378        MarkerFillColors: std::ptr::null_mut(),
379        Size: 4.0,
380        Offset: 0,
381        Stride: crate::IMPLOT_AUTO,
382        Flags: sys::ImPlotItemFlags_None as _,
383    }
384}
385
386pub(crate) fn take_next_plot_spec() -> Option<sys::ImPlotSpec_c> {
387    NEXT_PLOT_SPEC.with(|cell| cell.borrow_mut().take())
388}
389
390pub(crate) fn set_next_plot_spec(spec: Option<sys::ImPlotSpec_c>) {
391    NEXT_PLOT_SPEC.with(|cell| {
392        *cell.borrow_mut() = spec;
393    })
394}
395
396pub(crate) fn plot_spec_from(flags: u32, offset: i32, stride: i32) -> sys::ImPlotSpec_c {
397    let mut spec = take_next_plot_spec().unwrap_or_else(default_plot_spec);
398    spec.Flags = flags as sys::ImPlotItemFlags;
399    spec.Offset = offset;
400    spec.Stride = stride;
401    spec
402}
403
404pub(crate) fn plot_spec_with_style(
405    style: PlotItemStyle,
406    flags: u32,
407    offset: i32,
408    stride: i32,
409) -> sys::ImPlotSpec_c {
410    let mut spec = plot_spec_from(flags, offset, stride);
411    style.apply_to_spec(&mut spec);
412    spec
413}
414
415/// Universal plot builder that can create any plot type
416pub struct PlotBuilder<'a> {
417    plot_type: PlotType<'a>,
418}
419
420/// Enum representing different plot types
421pub enum PlotType<'a> {
422    Line {
423        label: &'a str,
424        x_data: &'a [f64],
425        y_data: &'a [f64],
426    },
427    Scatter {
428        label: &'a str,
429        x_data: &'a [f64],
430        y_data: &'a [f64],
431    },
432    Bar {
433        label: &'a str,
434        values: &'a [f64],
435        width: f64,
436    },
437    Histogram {
438        label: &'a str,
439        values: &'a [f64],
440        bins: i32,
441    },
442    Heatmap {
443        label: &'a str,
444        values: &'a [f64],
445        rows: usize,
446        cols: usize,
447    },
448    PieChart {
449        labels: Vec<&'a str>,
450        values: &'a [f64],
451        center: (f64, f64),
452        radius: f64,
453    },
454    Polygon {
455        label: &'a str,
456        x_data: &'a [f64],
457        y_data: &'a [f64],
458    },
459}
460
461impl<'a> PlotBuilder<'a> {
462    /// Create a line plot
463    pub fn line(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
464        Self {
465            plot_type: PlotType::Line {
466                label,
467                x_data,
468                y_data,
469            },
470        }
471    }
472
473    /// Create a scatter plot
474    pub fn scatter(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
475        Self {
476            plot_type: PlotType::Scatter {
477                label,
478                x_data,
479                y_data,
480            },
481        }
482    }
483
484    /// Create a bar plot
485    pub fn bar(label: &'a str, values: &'a [f64]) -> Self {
486        Self {
487            plot_type: PlotType::Bar {
488                label,
489                values,
490                width: 0.67,
491            },
492        }
493    }
494
495    /// Create a histogram
496    pub fn histogram(label: &'a str, values: &'a [f64]) -> Self {
497        Self {
498            plot_type: PlotType::Histogram {
499                label,
500                values,
501                bins: crate::BinMethod::Sturges as i32,
502            },
503        }
504    }
505
506    /// Create a heatmap
507    pub fn heatmap(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
508        Self {
509            plot_type: PlotType::Heatmap {
510                label,
511                values,
512                rows,
513                cols,
514            },
515        }
516    }
517
518    /// Create a pie chart
519    pub fn pie_chart(
520        labels: Vec<&'a str>,
521        values: &'a [f64],
522        center: (f64, f64),
523        radius: f64,
524    ) -> Self {
525        Self {
526            plot_type: PlotType::PieChart {
527                labels,
528                values,
529                center,
530                radius,
531            },
532        }
533    }
534
535    /// Create a polygon plot.
536    pub fn polygon(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
537        Self {
538            plot_type: PlotType::Polygon {
539                label,
540                x_data,
541                y_data,
542            },
543        }
544    }
545
546    /// Build and plot the chart
547    pub fn build(self) -> Result<(), PlotError> {
548        match self.plot_type {
549            PlotType::Line {
550                label,
551                x_data,
552                y_data,
553            } => {
554                let plot = line::LinePlot::new(label, x_data, y_data);
555                plot.validate()?;
556                plot.plot();
557            }
558            PlotType::Scatter {
559                label,
560                x_data,
561                y_data,
562            } => {
563                let plot = scatter::ScatterPlot::new(label, x_data, y_data);
564                plot.validate()?;
565                plot.plot();
566            }
567            PlotType::Bar {
568                label,
569                values,
570                width,
571            } => {
572                let plot = bar::BarPlot::new(label, values).with_bar_size(width);
573                plot.validate()?;
574                plot.plot();
575            }
576            PlotType::Histogram {
577                label,
578                values,
579                bins,
580            } => {
581                let plot = histogram::HistogramPlot::new(label, values).with_bins(bins);
582                plot.validate()?;
583                plot.plot();
584            }
585            PlotType::Heatmap {
586                label,
587                values,
588                rows,
589                cols,
590            } => {
591                let plot = heatmap::HeatmapPlot::new(label, values, rows, cols);
592                plot.validate()?;
593                plot.plot();
594            }
595            PlotType::PieChart {
596                labels,
597                values,
598                center,
599                radius,
600            } => {
601                let plot = pie::PieChartPlot::new(labels, values, center.0, center.1, radius);
602                plot.validate()?;
603                plot.plot();
604            }
605            PlotType::Polygon {
606                label,
607                x_data,
608                y_data,
609            } => {
610                let plot = polygon::PolygonPlot::new(label, x_data, y_data);
611                plot.validate()?;
612                plot.plot();
613            }
614        }
615        Ok(())
616    }
617}