Skip to main content

dear_implot/plots/
bar.rs

1//! Bar plot implementation
2
3use super::{Plot, PlotError, plot_spec_from, with_plot_str_or_empty};
4use crate::{BarsFlags, ItemFlags, sys};
5
6/// Builder for bar plots with customization options
7pub struct BarPlot<'a> {
8    label: &'a str,
9    values: &'a [f64],
10    bar_size: f64,
11    shift: f64,
12    flags: BarsFlags,
13    item_flags: ItemFlags,
14    offset: i32,
15    stride: i32,
16}
17
18impl<'a> BarPlot<'a> {
19    /// Create a new bar plot with the given label and values
20    pub fn new(label: &'a str, values: &'a [f64]) -> Self {
21        Self {
22            label,
23            values,
24            bar_size: 0.67, // Default bar width
25            shift: 0.0,
26            flags: BarsFlags::NONE,
27            item_flags: ItemFlags::NONE,
28            offset: 0,
29            stride: std::mem::size_of::<f64>() as i32,
30        }
31    }
32
33    /// Set the bar width (in plot units)
34    pub fn with_bar_size(mut self, bar_size: f64) -> Self {
35        self.bar_size = bar_size;
36        self
37    }
38
39    /// Set the bar shift (in plot units)
40    pub fn with_shift(mut self, shift: f64) -> Self {
41        self.shift = shift;
42        self
43    }
44
45    /// Set bar flags for customization
46    pub fn with_flags(mut self, flags: BarsFlags) -> Self {
47        self.flags = flags;
48        self
49    }
50
51    /// Set common item flags for this plot item (applies to all plot types)
52    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
53        self.item_flags = flags;
54        self
55    }
56
57    /// Set data offset for partial plotting
58    pub fn with_offset(mut self, offset: i32) -> Self {
59        self.offset = offset;
60        self
61    }
62
63    /// Set data stride for non-contiguous data
64    pub fn with_stride(mut self, stride: i32) -> Self {
65        self.stride = stride;
66        self
67    }
68
69    /// Validate the plot data
70    pub fn validate(&self) -> Result<(), PlotError> {
71        if self.values.is_empty() {
72            Err(PlotError::EmptyData)
73        } else {
74            Ok(())
75        }
76    }
77}
78
79impl<'a> Plot for BarPlot<'a> {
80    fn plot(&self) {
81        if self.validate().is_err() {
82            return; // Skip plotting if data is invalid
83        }
84        let Ok(count) = i32::try_from(self.values.len()) else {
85            return;
86        };
87
88        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
89            let spec = plot_spec_from(
90                self.flags.bits() | self.item_flags.bits(),
91                self.offset,
92                self.stride,
93            );
94            sys::ImPlot_PlotBars_doublePtrInt(
95                label_ptr,
96                self.values.as_ptr(),
97                count,
98                self.bar_size,
99                self.shift,
100                spec,
101            );
102        })
103    }
104
105    fn label(&self) -> &str {
106        self.label
107    }
108}
109
110/// Bar plot with explicit X positions
111pub struct PositionalBarPlot<'a> {
112    label: &'a str,
113    x_data: &'a [f64],
114    y_data: &'a [f64],
115    bar_size: f64,
116    flags: BarsFlags,
117    item_flags: ItemFlags,
118}
119
120impl<'a> PositionalBarPlot<'a> {
121    /// Create a new positional bar plot with explicit X and Y data
122    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
123        Self {
124            label,
125            x_data,
126            y_data,
127            bar_size: 0.67,
128            flags: BarsFlags::NONE,
129            item_flags: ItemFlags::NONE,
130        }
131    }
132
133    /// Set the bar width (in plot units)
134    pub fn with_bar_size(mut self, bar_size: f64) -> Self {
135        self.bar_size = bar_size;
136        self
137    }
138
139    /// Set bar flags for customization
140    pub fn with_flags(mut self, flags: BarsFlags) -> Self {
141        self.flags = flags;
142        self
143    }
144
145    /// Set common item flags for this plot item (applies to all plot types)
146    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
147        self.item_flags = flags;
148        self
149    }
150
151    /// Validate the plot data
152    pub fn validate(&self) -> Result<(), PlotError> {
153        super::validate_data_lengths(self.x_data, self.y_data)
154    }
155}
156
157impl<'a> Plot for PositionalBarPlot<'a> {
158    fn plot(&self) {
159        if self.validate().is_err() {
160            return; // Skip plotting if data is invalid
161        }
162        let Ok(count) = i32::try_from(self.y_data.len()) else {
163            return;
164        };
165
166        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
167            let spec = plot_spec_from(
168                self.flags.bits() | self.item_flags.bits(),
169                0,
170                std::mem::size_of::<f64>() as i32,
171            );
172            sys::ImPlot_PlotBars_doublePtrdoublePtr(
173                label_ptr,
174                self.x_data.as_ptr(),
175                self.y_data.as_ptr(),
176                count,
177                self.bar_size,
178                spec,
179            );
180        })
181    }
182
183    fn label(&self) -> &str {
184        self.label
185    }
186}
187
188/// Convenience functions for quick bar plotting
189impl<'ui> crate::PlotUi<'ui> {
190    /// Plot a bar chart with values (X will be indices)
191    pub fn bar_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
192        let plot = BarPlot::new(label, values);
193        plot.validate()?;
194        plot.plot();
195        Ok(())
196    }
197
198    /// Plot a bar chart with custom bar width
199    pub fn bar_plot_with_width(
200        &self,
201        label: &str,
202        values: &[f64],
203        width: f64,
204    ) -> Result<(), PlotError> {
205        let plot = BarPlot::new(label, values).with_bar_size(width);
206        plot.validate()?;
207        plot.plot();
208        Ok(())
209    }
210
211    /// Plot a positional bar chart with explicit X and Y data
212    pub fn positional_bar_plot(
213        &self,
214        label: &str,
215        x_data: &[f64],
216        y_data: &[f64],
217    ) -> Result<(), PlotError> {
218        let plot = PositionalBarPlot::new(label, x_data, y_data);
219        plot.validate()?;
220        plot.plot();
221        Ok(())
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_bar_plot_creation() {
231        let values = [1.0, 2.0, 3.0, 4.0];
232        let plot = BarPlot::new("test", &values);
233        assert_eq!(plot.label(), "test");
234        assert!(plot.validate().is_ok());
235    }
236
237    #[test]
238    fn test_bar_plot_empty_data() {
239        let values: &[f64] = &[];
240        let plot = BarPlot::new("test", values);
241        assert!(plot.validate().is_err());
242    }
243
244    #[test]
245    fn test_positional_bar_plot() {
246        let x_data = [1.0, 2.0, 3.0, 4.0];
247        let y_data = [1.0, 4.0, 2.0, 3.0];
248
249        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
250        assert_eq!(plot.label(), "test");
251        assert!(plot.validate().is_ok());
252    }
253
254    #[test]
255    fn test_positional_bar_plot_validation() {
256        let x_data = [1.0, 2.0, 3.0];
257        let y_data = [1.0, 4.0]; // Different length
258
259        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
260        assert!(plot.validate().is_err());
261    }
262}