Skip to main content

dear_implot/plots/
bar_groups.rs

1//! Bar groups plot implementation
2
3use super::{
4    PlotData, PlotDataLayout, PlotError, PlotItemStyle, plot_spec_with_style, with_plot_str_slice,
5};
6use crate::{BarGroupsFlags, ItemFlags, sys};
7
8/// Builder for bar groups plots with extensive customization options
9pub struct BarGroupsPlot<'a> {
10    label_ids: Vec<&'a str>,
11    values: &'a [f64],
12    style: PlotItemStyle,
13    item_count: usize,
14    group_count: usize,
15    group_size: f64,
16    shift: f64,
17    flags: BarGroupsFlags,
18    item_flags: ItemFlags,
19}
20
21impl<'a> super::PlotItemStyled for BarGroupsPlot<'a> {
22    fn style_mut(&mut self) -> &mut PlotItemStyle {
23        &mut self.style
24    }
25}
26
27impl<'a> BarGroupsPlot<'a> {
28    /// Create a new bar groups plot
29    ///
30    /// # Arguments
31    /// * `label_ids` - Labels for each item in the group
32    /// * `values` - Values in row-major order (item_count rows, group_count cols)
33    /// * `item_count` - Number of items (series) in each group
34    /// * `group_count` - Number of groups
35    pub fn new(
36        label_ids: Vec<&'a str>,
37        values: &'a [f64],
38        item_count: usize,
39        group_count: usize,
40    ) -> Self {
41        Self {
42            label_ids,
43            values,
44            style: PlotItemStyle::default(),
45            item_count,
46            group_count,
47            group_size: 0.67,
48            shift: 0.0,
49            flags: BarGroupsFlags::NONE,
50            item_flags: ItemFlags::NONE,
51        }
52    }
53
54    /// Set the group size (width of each group)
55    pub fn with_group_size(mut self, group_size: f64) -> Self {
56        self.group_size = group_size;
57        self
58    }
59
60    /// Set ImPlotSpec-backed style overrides for this bar groups plot.
61    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
62        self.style = style;
63        self
64    }
65
66    /// Set the shift (horizontal offset)
67    pub fn with_shift(mut self, shift: f64) -> Self {
68        self.shift = shift;
69        self
70    }
71
72    /// Set bar groups flags for customization
73    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
74        self.flags = flags;
75        self
76    }
77
78    /// Set common item flags for this plot item (applies to all plot types)
79    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
80        self.item_flags = flags;
81        self
82    }
83
84    /// Make bars horizontal instead of vertical
85    pub fn horizontal(mut self) -> Self {
86        self.flags |= BarGroupsFlags::HORIZONTAL;
87        self
88    }
89
90    /// Stack bars instead of grouping them side by side
91    pub fn stacked(mut self) -> Self {
92        self.flags |= BarGroupsFlags::STACKED;
93        self
94    }
95
96    /// Validate the plot data
97    pub fn validate(&self) -> Result<(), PlotError> {
98        if self.label_ids.len() != self.item_count {
99            return Err(PlotError::InvalidData(format!(
100                "Label count ({}) must match item count ({})",
101                self.label_ids.len(),
102                self.item_count
103            )));
104        }
105
106        let expected_values = self
107            .item_count
108            .checked_mul(self.group_count)
109            .ok_or_else(|| {
110                PlotError::InvalidData("item_count * group_count overflowed usize".to_string())
111            })?;
112        if self.values.len() != expected_values {
113            return Err(PlotError::InvalidData(format!(
114                "Values length ({}) must equal item_count * group_count ({})",
115                self.values.len(),
116                expected_values
117            )));
118        }
119
120        if self.item_count == 0 || self.group_count == 0 {
121            return Err(PlotError::EmptyData);
122        }
123
124        let _ = i32::try_from(self.item_count).map_err(|_| {
125            PlotError::InvalidData("item_count exceeded ImPlot's i32 range".to_string())
126        })?;
127        let _ = i32::try_from(self.group_count).map_err(|_| {
128            PlotError::InvalidData("group_count exceeded ImPlot's i32 range".to_string())
129        })?;
130
131        Ok(())
132    }
133
134    /// Plot the bar groups
135    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
136        if self.validate().is_err() {
137            return;
138        }
139        let Ok(item_count) = i32::try_from(self.item_count) else {
140            return;
141        };
142        let Ok(group_count) = i32::try_from(self.group_count) else {
143            return;
144        };
145        plot_ui.with_bound_context(|| {
146            with_plot_str_slice(&self.label_ids, |label_ptrs| unsafe {
147                let spec = plot_spec_with_style(
148                    self.style,
149                    self.flags.bits() | self.item_flags.bits(),
150                    PlotDataLayout::DEFAULT,
151                );
152                sys::ImPlot_PlotBarGroups_doublePtr(
153                    label_ptrs.as_ptr(),
154                    self.values.as_ptr(),
155                    item_count,
156                    group_count,
157                    self.group_size,
158                    self.shift,
159                    spec,
160                );
161            })
162        })
163    }
164}
165
166impl<'a> PlotData for BarGroupsPlot<'a> {
167    fn label(&self) -> &str {
168        "BarGroups" // Generic label for groups
169    }
170
171    fn data_len(&self) -> usize {
172        self.values.len()
173    }
174}
175
176/// Bar groups plot for f32 data
177pub struct BarGroupsPlotF32<'a> {
178    label_ids: Vec<&'a str>,
179    values: &'a [f32],
180    style: PlotItemStyle,
181    item_count: usize,
182    group_count: usize,
183    group_size: f64,
184    shift: f64,
185    flags: BarGroupsFlags,
186    item_flags: ItemFlags,
187}
188
189impl<'a> super::PlotItemStyled for BarGroupsPlotF32<'a> {
190    fn style_mut(&mut self) -> &mut PlotItemStyle {
191        &mut self.style
192    }
193}
194
195impl<'a> BarGroupsPlotF32<'a> {
196    /// Create a new bar groups plot with f32 data
197    pub fn new(
198        label_ids: Vec<&'a str>,
199        values: &'a [f32],
200        item_count: usize,
201        group_count: usize,
202    ) -> Self {
203        Self {
204            label_ids,
205            values,
206            style: PlotItemStyle::default(),
207            item_count,
208            group_count,
209            group_size: 0.67,
210            shift: 0.0,
211            flags: BarGroupsFlags::NONE,
212            item_flags: ItemFlags::NONE,
213        }
214    }
215
216    /// Set the group size
217    pub fn with_group_size(mut self, group_size: f64) -> Self {
218        self.group_size = group_size;
219        self
220    }
221
222    /// Set ImPlotSpec-backed style overrides for this bar groups plot.
223    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
224        self.style = style;
225        self
226    }
227
228    /// Set the shift
229    pub fn with_shift(mut self, shift: f64) -> Self {
230        self.shift = shift;
231        self
232    }
233
234    /// Set flags
235    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
236        self.flags = flags;
237        self
238    }
239
240    /// Set common item flags for this plot item (applies to all plot types)
241    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
242        self.item_flags = flags;
243        self
244    }
245
246    /// Make bars horizontal
247    pub fn horizontal(mut self) -> Self {
248        self.flags |= BarGroupsFlags::HORIZONTAL;
249        self
250    }
251
252    /// Stack bars
253    pub fn stacked(mut self) -> Self {
254        self.flags |= BarGroupsFlags::STACKED;
255        self
256    }
257
258    /// Validate the plot data
259    pub fn validate(&self) -> Result<(), PlotError> {
260        if self.label_ids.len() != self.item_count {
261            return Err(PlotError::InvalidData(format!(
262                "Label count ({}) must match item count ({})",
263                self.label_ids.len(),
264                self.item_count
265            )));
266        }
267
268        let expected_values = self
269            .item_count
270            .checked_mul(self.group_count)
271            .ok_or_else(|| {
272                PlotError::InvalidData("item_count * group_count overflowed usize".to_string())
273            })?;
274        if self.values.len() != expected_values {
275            return Err(PlotError::InvalidData(format!(
276                "Values length ({}) must equal item_count * group_count ({})",
277                self.values.len(),
278                expected_values
279            )));
280        }
281
282        if self.item_count == 0 || self.group_count == 0 {
283            return Err(PlotError::EmptyData);
284        }
285
286        let _ = i32::try_from(self.item_count).map_err(|_| {
287            PlotError::InvalidData("item_count exceeded ImPlot's i32 range".to_string())
288        })?;
289        let _ = i32::try_from(self.group_count).map_err(|_| {
290            PlotError::InvalidData("group_count exceeded ImPlot's i32 range".to_string())
291        })?;
292
293        Ok(())
294    }
295
296    /// Plot the bar groups
297    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
298        if self.validate().is_err() {
299            return;
300        }
301        let Ok(item_count) = i32::try_from(self.item_count) else {
302            return;
303        };
304        let Ok(group_count) = i32::try_from(self.group_count) else {
305            return;
306        };
307        plot_ui.with_bound_context(|| {
308            with_plot_str_slice(&self.label_ids, |label_ptrs| unsafe {
309                let spec = plot_spec_with_style(
310                    self.style,
311                    self.flags.bits() | self.item_flags.bits(),
312                    PlotDataLayout::DEFAULT,
313                );
314                sys::ImPlot_PlotBarGroups_FloatPtr(
315                    label_ptrs.as_ptr(),
316                    self.values.as_ptr(),
317                    item_count,
318                    group_count,
319                    self.group_size,
320                    self.shift,
321                    spec,
322                );
323            })
324        })
325    }
326}
327
328impl<'a> PlotData for BarGroupsPlotF32<'a> {
329    fn label(&self) -> &str {
330        "BarGroups" // Generic label for groups
331    }
332
333    fn data_len(&self) -> usize {
334        self.values.len()
335    }
336}
337
338/// Simple bar groups plot with automatic layout
339pub struct SimpleBarGroupsPlot<'a> {
340    labels: Vec<&'a str>,
341    data: Vec<Vec<f64>>,
342    style: PlotItemStyle,
343    group_size: f64,
344    flags: BarGroupsFlags,
345    item_flags: ItemFlags,
346}
347
348impl<'a> super::PlotItemStyled for SimpleBarGroupsPlot<'a> {
349    fn style_mut(&mut self) -> &mut PlotItemStyle {
350        &mut self.style
351    }
352}
353
354impl<'a> SimpleBarGroupsPlot<'a> {
355    /// Create a simple bar groups plot from a 2D data structure
356    ///
357    /// # Arguments
358    /// * `labels` - Labels for each series
359    /// * `data` - Vector of vectors, where each inner vector is a series
360    pub fn new(labels: Vec<&'a str>, data: Vec<Vec<f64>>) -> Self {
361        Self {
362            labels,
363            data,
364            style: PlotItemStyle::default(),
365            group_size: 0.67,
366            flags: BarGroupsFlags::NONE,
367            item_flags: ItemFlags::NONE,
368        }
369    }
370
371    /// Set ImPlotSpec-backed style overrides for this bar groups plot.
372    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
373        self.style = style;
374        self
375    }
376
377    /// Set the group size
378    pub fn with_group_size(mut self, group_size: f64) -> Self {
379        self.group_size = group_size;
380        self
381    }
382
383    /// Set flags
384    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
385        self.flags = flags;
386        self
387    }
388
389    /// Set common item flags for this plot item (applies to all plot types)
390    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
391        self.item_flags = flags;
392        self
393    }
394
395    /// Make bars horizontal
396    pub fn horizontal(mut self) -> Self {
397        self.flags |= BarGroupsFlags::HORIZONTAL;
398        self
399    }
400
401    /// Stack bars
402    pub fn stacked(mut self) -> Self {
403        self.flags |= BarGroupsFlags::STACKED;
404        self
405    }
406
407    /// Plot the bar groups
408    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
409        if self.data.is_empty() || self.labels.is_empty() {
410            return;
411        }
412
413        let item_count = self.data.len();
414        let group_count = self.data[0].len();
415
416        // Flatten data into row-major order
417        let mut flattened_data = Vec::with_capacity(item_count * group_count);
418        for group_idx in 0..group_count {
419            for item_idx in 0..item_count {
420                if group_idx < self.data[item_idx].len() {
421                    flattened_data.push(self.data[item_idx][group_idx]);
422                } else {
423                    flattened_data.push(0.0); // Fill missing data with zeros
424                }
425            }
426        }
427
428        let plot = BarGroupsPlot::new(self.labels, &flattened_data, item_count, group_count)
429            .with_style(self.style)
430            .with_group_size(self.group_size)
431            .with_flags(self.flags)
432            .with_item_flags(self.item_flags);
433
434        plot.plot(plot_ui);
435    }
436}