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    ///
74    /// # Safety
75    ///
76    /// Every sample address computed from `layout` must refer to an initialized, properly aligned
77    /// `f64` within the allocation backing this builder's values.
78    pub unsafe fn with_data_layout(mut self, layout: PlotDataLayout) -> Self {
79        self.layout = layout;
80        self
81    }
82
83    /// Set the sample-index offset used to read values.
84    pub fn with_offset(mut self, offset: PlotDataOffset) -> Self {
85        self.layout = self.layout.with_offset(offset);
86        self
87    }
88
89    /// Set the byte stride used to read values.
90    ///
91    /// # Safety
92    ///
93    /// Every strided sample read must remain initialized, aligned, and within the allocation
94    /// backing this builder's values.
95    pub unsafe fn with_stride(mut self, stride: PlotDataStride) -> Self {
96        self.layout = self.layout.with_stride(stride);
97        self
98    }
99
100    /// Validate the plot data
101    pub fn validate(&self) -> Result<(), PlotError> {
102        if self.values.is_empty() {
103            Err(PlotError::EmptyData)
104        } else {
105            Ok(())
106        }
107    }
108}
109
110impl<'a> Plot for BarPlot<'a> {
111    fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
112        if self.validate().is_err() {
113            return; // Skip plotting if data is invalid
114        }
115        let Ok(count) = i32::try_from(self.values.len()) else {
116            return;
117        };
118
119        plot_ui.with_bound_context(|| {
120            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
121                let spec = plot_spec_with_style(
122                    self.style,
123                    self.flags.bits() | self.item_flags.bits(),
124                    self.layout,
125                );
126                sys::ImPlot_PlotBars_doublePtrInt(
127                    label_ptr,
128                    self.values.as_ptr(),
129                    count,
130                    self.bar_size,
131                    self.shift,
132                    spec,
133                );
134            })
135        })
136    }
137
138    fn label(&self) -> &str {
139        self.label
140    }
141}
142
143/// Bar plot with explicit X positions
144pub struct PositionalBarPlot<'a> {
145    label: &'a str,
146    x_data: &'a [f64],
147    y_data: &'a [f64],
148    style: PlotItemStyle,
149    bar_size: f64,
150    flags: BarsFlags,
151    item_flags: ItemFlags,
152}
153
154impl<'a> super::PlotItemStyled for PositionalBarPlot<'a> {
155    fn style_mut(&mut self) -> &mut PlotItemStyle {
156        &mut self.style
157    }
158}
159
160impl<'a> PositionalBarPlot<'a> {
161    /// Create a new positional bar plot with explicit X and Y data
162    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
163        Self {
164            label,
165            x_data,
166            y_data,
167            style: PlotItemStyle::default(),
168            bar_size: 0.67,
169            flags: BarsFlags::NONE,
170            item_flags: ItemFlags::NONE,
171        }
172    }
173
174    /// Set the bar width (in plot units)
175    pub fn with_bar_size(mut self, bar_size: f64) -> Self {
176        self.bar_size = bar_size;
177        self
178    }
179
180    /// Set ImPlotSpec-backed style overrides for this positional bar plot.
181    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
182        self.style = style;
183        self
184    }
185
186    /// Set bar flags for customization
187    pub fn with_flags(mut self, flags: BarsFlags) -> Self {
188        self.flags = flags;
189        self
190    }
191
192    /// Set common item flags for this plot item (applies to all plot types)
193    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
194        self.item_flags = flags;
195        self
196    }
197
198    /// Validate the plot data
199    pub fn validate(&self) -> Result<(), PlotError> {
200        super::validate_data_lengths(self.x_data, self.y_data)
201    }
202}
203
204impl<'a> Plot for PositionalBarPlot<'a> {
205    fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
206        if self.validate().is_err() {
207            return; // Skip plotting if data is invalid
208        }
209        let Ok(count) = i32::try_from(self.y_data.len()) else {
210            return;
211        };
212
213        plot_ui.with_bound_context(|| {
214            with_plot_str_or_empty(self.label, |label_ptr| unsafe {
215                let spec = plot_spec_with_style(
216                    self.style,
217                    self.flags.bits() | self.item_flags.bits(),
218                    PlotDataLayout::DEFAULT,
219                );
220                sys::ImPlot_PlotBars_doublePtrdoublePtr(
221                    label_ptr,
222                    self.x_data.as_ptr(),
223                    self.y_data.as_ptr(),
224                    count,
225                    self.bar_size,
226                    spec,
227                );
228            })
229        })
230    }
231
232    fn label(&self) -> &str {
233        self.label
234    }
235}
236
237/// Convenience functions for quick bar plotting
238impl<'ui> crate::PlotUi<'ui> {
239    /// Plot a bar chart with values (X will be indices)
240    pub fn bar_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
241        let plot = BarPlot::new(label, values);
242        plot.validate()?;
243        plot.plot(self);
244        Ok(())
245    }
246
247    /// Plot a bar chart with custom bar width
248    pub fn bar_plot_with_width(
249        &self,
250        label: &str,
251        values: &[f64],
252        width: f64,
253    ) -> Result<(), PlotError> {
254        let plot = BarPlot::new(label, values).with_bar_size(width);
255        plot.validate()?;
256        plot.plot(self);
257        Ok(())
258    }
259
260    /// Plot a positional bar chart with explicit X and Y data
261    pub fn positional_bar_plot(
262        &self,
263        label: &str,
264        x_data: &[f64],
265        y_data: &[f64],
266    ) -> Result<(), PlotError> {
267        let plot = PositionalBarPlot::new(label, x_data, y_data);
268        plot.validate()?;
269        plot.plot(self);
270        Ok(())
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::plots::PlotItemStyled;
278
279    #[test]
280    fn test_bar_plot_creation() {
281        let values = [1.0, 2.0, 3.0, 4.0];
282        let plot = BarPlot::new("test", &values);
283        assert_eq!(plot.label(), "test");
284        assert!(plot.validate().is_ok());
285    }
286
287    #[test]
288    fn test_bar_plot_empty_data() {
289        let values: &[f64] = &[];
290        let plot = BarPlot::new("test", values);
291        assert!(plot.validate().is_err());
292    }
293
294    #[test]
295    fn test_positional_bar_plot() {
296        let x_data = [1.0, 2.0, 3.0, 4.0];
297        let y_data = [1.0, 4.0, 2.0, 3.0];
298
299        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
300        assert_eq!(plot.label(), "test");
301        assert!(plot.validate().is_ok());
302    }
303
304    #[test]
305    fn test_positional_bar_plot_validation() {
306        let x_data = [1.0, 2.0, 3.0];
307        let y_data = [1.0, 4.0]; // Different length
308
309        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
310        assert!(plot.validate().is_err());
311    }
312
313    #[test]
314    fn test_bar_plot_style_trait_builders() {
315        let values = [1.0, 2.0, 3.0, 4.0];
316        let plot = BarPlot::new("styled", &values)
317            .with_line_color([0.1, 0.2, 0.3, 0.4])
318            .with_fill_color([0.4, 0.3, 0.2, 0.1])
319            .with_fill_alpha(0.6)
320            .with_line_weight(2.5);
321
322        assert_eq!(
323            plot.style.line_color,
324            Some(sys::ImVec4_c {
325                x: 0.1,
326                y: 0.2,
327                z: 0.3,
328                w: 0.4,
329            })
330        );
331        assert_eq!(
332            plot.style.fill_color,
333            Some(sys::ImVec4_c {
334                x: 0.4,
335                y: 0.3,
336                z: 0.2,
337                w: 0.1,
338            })
339        );
340        assert_eq!(plot.style.fill_alpha, Some(0.6));
341        assert_eq!(plot.style.line_weight, Some(2.5));
342    }
343}