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, plot_ui: &crate::PlotUi<'_>) {
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        let _guard = plot_ui.bind();
110        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
111            let spec = plot_spec_with_style(
112                self.style,
113                self.flags.bits() | self.item_flags.bits(),
114                self.layout,
115            );
116            sys::ImPlot_PlotBars_doublePtrInt(
117                label_ptr,
118                self.values.as_ptr(),
119                count,
120                self.bar_size,
121                self.shift,
122                spec,
123            );
124        })
125    }
126
127    fn label(&self) -> &str {
128        self.label
129    }
130}
131
132/// Bar plot with explicit X positions
133pub struct PositionalBarPlot<'a> {
134    label: &'a str,
135    x_data: &'a [f64],
136    y_data: &'a [f64],
137    style: PlotItemStyle,
138    bar_size: f64,
139    flags: BarsFlags,
140    item_flags: ItemFlags,
141}
142
143impl<'a> super::PlotItemStyled for PositionalBarPlot<'a> {
144    fn style_mut(&mut self) -> &mut PlotItemStyle {
145        &mut self.style
146    }
147}
148
149impl<'a> PositionalBarPlot<'a> {
150    /// Create a new positional bar plot with explicit X and Y data
151    pub fn new(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
152        Self {
153            label,
154            x_data,
155            y_data,
156            style: PlotItemStyle::default(),
157            bar_size: 0.67,
158            flags: BarsFlags::NONE,
159            item_flags: ItemFlags::NONE,
160        }
161    }
162
163    /// Set the bar width (in plot units)
164    pub fn with_bar_size(mut self, bar_size: f64) -> Self {
165        self.bar_size = bar_size;
166        self
167    }
168
169    /// Set ImPlotSpec-backed style overrides for this positional bar plot.
170    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
171        self.style = style;
172        self
173    }
174
175    /// Set bar flags for customization
176    pub fn with_flags(mut self, flags: BarsFlags) -> Self {
177        self.flags = flags;
178        self
179    }
180
181    /// Set common item flags for this plot item (applies to all plot types)
182    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
183        self.item_flags = flags;
184        self
185    }
186
187    /// Validate the plot data
188    pub fn validate(&self) -> Result<(), PlotError> {
189        super::validate_data_lengths(self.x_data, self.y_data)
190    }
191}
192
193impl<'a> Plot for PositionalBarPlot<'a> {
194    fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
195        if self.validate().is_err() {
196            return; // Skip plotting if data is invalid
197        }
198        let Ok(count) = i32::try_from(self.y_data.len()) else {
199            return;
200        };
201
202        let _guard = plot_ui.bind();
203        with_plot_str_or_empty(self.label, |label_ptr| unsafe {
204            let spec = plot_spec_with_style(
205                self.style,
206                self.flags.bits() | self.item_flags.bits(),
207                PlotDataLayout::DEFAULT,
208            );
209            sys::ImPlot_PlotBars_doublePtrdoublePtr(
210                label_ptr,
211                self.x_data.as_ptr(),
212                self.y_data.as_ptr(),
213                count,
214                self.bar_size,
215                spec,
216            );
217        })
218    }
219
220    fn label(&self) -> &str {
221        self.label
222    }
223}
224
225/// Convenience functions for quick bar plotting
226impl<'ui> crate::PlotUi<'ui> {
227    /// Plot a bar chart with values (X will be indices)
228    pub fn bar_plot(&self, label: &str, values: &[f64]) -> Result<(), PlotError> {
229        let plot = BarPlot::new(label, values);
230        plot.validate()?;
231        plot.plot(self);
232        Ok(())
233    }
234
235    /// Plot a bar chart with custom bar width
236    pub fn bar_plot_with_width(
237        &self,
238        label: &str,
239        values: &[f64],
240        width: f64,
241    ) -> Result<(), PlotError> {
242        let plot = BarPlot::new(label, values).with_bar_size(width);
243        plot.validate()?;
244        plot.plot(self);
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        plot.plot(self);
258        Ok(())
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use crate::plots::PlotItemStyled;
266
267    #[test]
268    fn test_bar_plot_creation() {
269        let values = [1.0, 2.0, 3.0, 4.0];
270        let plot = BarPlot::new("test", &values);
271        assert_eq!(plot.label(), "test");
272        assert!(plot.validate().is_ok());
273    }
274
275    #[test]
276    fn test_bar_plot_empty_data() {
277        let values: &[f64] = &[];
278        let plot = BarPlot::new("test", values);
279        assert!(plot.validate().is_err());
280    }
281
282    #[test]
283    fn test_positional_bar_plot() {
284        let x_data = [1.0, 2.0, 3.0, 4.0];
285        let y_data = [1.0, 4.0, 2.0, 3.0];
286
287        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
288        assert_eq!(plot.label(), "test");
289        assert!(plot.validate().is_ok());
290    }
291
292    #[test]
293    fn test_positional_bar_plot_validation() {
294        let x_data = [1.0, 2.0, 3.0];
295        let y_data = [1.0, 4.0]; // Different length
296
297        let plot = PositionalBarPlot::new("test", &x_data, &y_data);
298        assert!(plot.validate().is_err());
299    }
300
301    #[test]
302    fn test_bar_plot_style_trait_builders() {
303        let values = [1.0, 2.0, 3.0, 4.0];
304        let plot = BarPlot::new("styled", &values)
305            .with_line_color([0.1, 0.2, 0.3, 0.4])
306            .with_fill_color([0.4, 0.3, 0.2, 0.1])
307            .with_fill_alpha(0.6)
308            .with_line_weight(2.5);
309
310        assert_eq!(
311            plot.style.line_color,
312            Some(sys::ImVec4_c {
313                x: 0.1,
314                y: 0.2,
315                z: 0.3,
316                w: 0.4,
317            })
318        );
319        assert_eq!(
320            plot.style.fill_color,
321            Some(sys::ImVec4_c {
322                x: 0.4,
323                y: 0.3,
324                z: 0.2,
325                w: 0.1,
326            })
327        );
328        assert_eq!(plot.style.fill_alpha, Some(0.6));
329        assert_eq!(plot.style.line_weight, Some(2.5));
330    }
331}