Skip to main content

dear_implot/plots/
bar.rs

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