Skip to main content

dear_implot/plots/
bar.rs

1//! Bar plot implementation
2
3use super::{
4    Plot, PlotDataLayout, PlotDataOffset, PlotDataStride, PlotError, PlotItemStyle,
5    plot_spec_with_style, with_plot_str_or_empty,
6};
7use crate::{BarsFlags, ItemFlags, sys};
8
9/// Builder for bar plots with customization options
10pub struct BarPlot<'a> {
11    label: &'a str,
12    values: &'a [f64],
13    style: PlotItemStyle,
14    bar_size: f64,
15    shift: f64,
16    flags: BarsFlags,
17    item_flags: ItemFlags,
18    layout: PlotDataLayout,
19}
20
21impl<'a> super::PlotItemStyled for BarPlot<'a> {
22    fn style_mut(&mut self) -> &mut PlotItemStyle {
23        &mut self.style
24    }
25}
26
27impl<'a> BarPlot<'a> {
28    /// Create a new bar plot with the given label and values
29    pub fn new(label: &'a str, values: &'a [f64]) -> Self {
30        Self {
31            label,
32            values,
33            style: PlotItemStyle::default(),
34            bar_size: 0.67, // Default bar width
35            shift: 0.0,
36            flags: BarsFlags::NONE,
37            item_flags: ItemFlags::NONE,
38            layout: PlotDataLayout::DEFAULT,
39        }
40    }
41
42    /// Set the bar width (in plot units)
43    pub fn with_bar_size(mut self, bar_size: f64) -> Self {
44        self.bar_size = bar_size;
45        self
46    }
47
48    /// Set ImPlotSpec-backed style overrides for this bar plot.
49    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
50        self.style = style;
51        self
52    }
53
54    /// Set the bar shift (in plot units)
55    pub fn with_shift(mut self, shift: f64) -> Self {
56        self.shift = shift;
57        self
58    }
59
60    /// Set bar flags for customization
61    pub fn with_flags(mut self, flags: BarsFlags) -> Self {
62        self.flags = flags;
63        self
64    }
65
66    /// Set common item flags for this plot item (applies to all plot types)
67    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
68        self.item_flags = flags;
69        self
70    }
71
72    /// Set the data layout used to read values.
73    pub fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
74        self.layout = layout;
75        self
76    }
77
78    /// Set the sample-index offset used to read values.
79    pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
80        self.layout = self.layout.with_offset(offset);
81        self
82    }
83
84    /// Set the byte stride used to read values.
85    pub fn with_stride(mut self, stride: PlotDataStride) -> Self {
86        self.layout = self.layout.with_stride(stride);
87        self
88    }
89
90    /// Validate the plot data
91    pub fn validate(&self) -> Result<(), PlotError> {
92        if self.values.is_empty() {
93            Err(PlotError::EmptyData)
94        } else {
95            Ok(())
96        }
97    }
98}
99
100impl<'a> Plot for BarPlot<'a> {
101    fn plot(&self) {
102        if self.validate().is_err() {
103            return; // Skip plotting if data is invalid
104        }
105        let Ok(count) = i32::try_from(self.values.len()) else {
106            return;
107        };
108
109        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
110            let spec = plot_spec_with_style(
111                self.style,
112                self.flags.bits() | self.item_flags.bits(),
113                self.layout,
114            );
115            sys::ImPlot_PlotBars_doublePtrInt(
116                label_ptr,
117                self.values.as_ptr(),
118                count,
119                self.bar_size,
120                self.shift,
121                spec,
122            );
123        })
124    }
125
126    fn label(&self) -> &str {
127        self.label
128    }
129}
130
131/// Bar plot with explicit X positions
132pub struct PositionalBarPlot<'a> {
133    label: &'a str,
134    x_data: &'a [f64],
135    y_data: &'a [f64],
136    style: PlotItemStyle,
137    bar_size: f64,
138    flags: BarsFlags,
139    item_flags: ItemFlags,
140}
141
142impl<'a> super::PlotItemStyled for PositionalBarPlot<'a> {
143    fn style_mut(&mut self) -> &mut PlotItemStyle {
144        &mut self.style
145    }
146}
147
148impl<'a> PositionalBarPlot<'a> {
149    /// Create a new positional bar plot with explicit X and Y data
150    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
151        Self {
152            label,
153            x_data,
154            y_data,
155            style: PlotItemStyle::default(),
156            bar_size: 0.67,
157            flags: BarsFlags::NONE,
158            item_flags: ItemFlags::NONE,
159        }
160    }
161
162    /// Set the bar width (in plot units)
163    pub fn with_bar_size(mut self, bar_size: f64) -> Self {
164        self.bar_size = bar_size;
165        self
166    }
167
168    /// Set ImPlotSpec-backed style overrides for this positional bar plot.
169    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
170        self.style = style;
171        self
172    }
173
174    /// Set bar flags for customization
175    pub fn with_flags(mut self, flags: BarsFlags) -> Self {
176        self.flags = flags;
177        self
178    }
179
180    /// Set common item flags for this plot item (applies to all plot types)
181    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
182        self.item_flags = flags;
183        self
184    }
185
186    /// Validate the plot data
187    pub fn validate(&self) -> Result<(), PlotError> {
188        super::validate_data_lengths(self.x_data, self.y_data)
189    }
190}
191
192impl<'a> Plot for PositionalBarPlot<'a> {
193    fn plot(&self) {
194        if self.validate().is_err() {
195            return; // Skip plotting if data is invalid
196        }
197        let Ok(count) = i32::try_from(self.y_data.len()) else {
198            return;
199        };
200
201        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
202            let spec = plot_spec_with_style(
203                self.style,
204                self.flags.bits() | self.item_flags.bits(),
205                PlotDataLayout::DEFAULT,
206            );
207            sys::ImPlot_PlotBars_doublePtrdoublePtr(
208                label_ptr,
209                self.x_data.as_ptr(),
210                self.y_data.as_ptr(),
211                count,
212                self.bar_size,
213                spec,
214            );
215        })
216    }
217
218    fn label(&self) -> &str {
219        self.label
220    }
221}
222
223/// Convenience functions for quick bar plotting
224impl<'ui> crate::PlotUi<'ui> {
225    /// Plot a bar chart with values (X will be indices)
226    pub fn bar_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
227        let plot = BarPlot::new(label, values);
228        plot.validate()?;
229        self.bind();
230        plot.plot();
231        Ok(())
232    }
233
234    /// Plot a bar chart with custom bar width
235    pub fn bar_plot_with_width(
236        &self,
237        label: &str,
238        values: &[f64],
239        width: f64,
240    ) -> Result<(), PlotError> {
241        let plot = BarPlot::new(label, values).with_bar_size(width);
242        plot.validate()?;
243        self.bind();
244        plot.plot();
245        Ok(())
246    }
247
248    /// Plot a positional bar chart with explicit X and Y data
249    pub fn positional_bar_plot(
250        &self,
251        label: &str,
252        x_data: &[f64],
253        y_data: &[f64],
254    ) -> Result<(), PlotError> {
255        let plot = PositionalBarPlot::new(label, x_data, y_data);
256        plot.validate()?;
257        self.bind();
258        plot.plot();
259        Ok(())
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use crate::plots::PlotItemStyled;
267
268    #[test]
269    fn test_bar_plot_creation() {
270        let values = [1.0, 2.0, 3.0, 4.0];
271        let plot = BarPlot::new("test", &values);
272        assert_eq!(plot.label(), "test");
273        assert!(plot.validate().is_ok());
274    }
275
276    #[test]
277    fn test_bar_plot_empty_data() {
278        let values: &[f64] = &[];
279        let plot = BarPlot::new("test", values);
280        assert!(plot.validate().is_err());
281    }
282
283    #[test]
284    fn test_positional_bar_plot() {
285        let x_data = [1.0, 2.0, 3.0, 4.0];
286        let y_data = [1.0, 4.0, 2.0, 3.0];
287
288        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
289        assert_eq!(plot.label(), "test");
290        assert!(plot.validate().is_ok());
291    }
292
293    #[test]
294    fn test_positional_bar_plot_validation() {
295        let x_data = [1.0, 2.0, 3.0];
296        let y_data = [1.0, 4.0]; // Different length
297
298        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
299        assert!(plot.validate().is_err());
300    }
301
302    #[test]
303    fn test_bar_plot_style_trait_builders() {
304        let values = [1.0, 2.0, 3.0, 4.0];
305        let plot = BarPlot::new("styled", &values)
306            .with_line_color([0.1, 0.2, 0.3, 0.4])
307            .with_fill_color([0.4, 0.3, 0.2, 0.1])
308            .with_fill_alpha(0.6)
309            .with_line_weight(2.5);
310
311        assert_eq!(
312            plot.style.line_color,
313            Some(sys::ImVec4_c {
314                x: 0.1,
315                y: 0.2,
316                z: 0.3,
317                w: 0.4,
318            })
319        );
320        assert_eq!(
321            plot.style.fill_color,
322            Some(sys::ImVec4_c {
323                x: 0.4,
324                y: 0.3,
325                z: 0.2,
326                w: 0.1,
327            })
328        );
329        assert_eq!(plot.style.fill_alpha, Some(0.6));
330        assert_eq!(plot.style.line_weight, Some(2.5));
331    }
332}