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        let _guard = plot_ui.bind();
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
165impl<'a> PlotData for BarGroupsPlot<'a> {
166    fn label(&self) -> &str {
167        "BarGroups" // Generic label for groups
168    }
169
170    fn data_len(&self) -> usize {
171        self.values.len()
172    }
173}
174
175/// Bar groups plot for f32 data
176pub struct BarGroupsPlotF32<'a> {
177    label_ids: Vec<&'a str>,
178    values: &'a [f32],
179    style: PlotItemStyle,
180    item_count: usize,
181    group_count: usize,
182    group_size: f64,
183    shift: f64,
184    flags: BarGroupsFlags,
185    item_flags: ItemFlags,
186}
187
188impl<'a> super::PlotItemStyled for BarGroupsPlotF32<'a> {
189    fn style_mut(&mut self) -> &mut PlotItemStyle {
190        &mut self.style
191    }
192}
193
194impl<'a> BarGroupsPlotF32<'a> {
195    /// Create a new bar groups plot with f32 data
196    pub fn new(
197        label_ids: Vec<&'a str>,
198        values: &'a [f32],
199        item_count: usize,
200        group_count: usize,
201    ) -> Self {
202        Self {
203            label_ids,
204            values,
205            style: PlotItemStyle::default(),
206            item_count,
207            group_count,
208            group_size: 0.67,
209            shift: 0.0,
210            flags: BarGroupsFlags::NONE,
211            item_flags: ItemFlags::NONE,
212        }
213    }
214
215    /// Set the group size
216    pub fn with_group_size(mut self, group_size: f64) -> Self {
217        self.group_size = group_size;
218        self
219    }
220
221    /// Set ImPlotSpec-backed style overrides for this bar groups plot.
222    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
223        self.style = style;
224        self
225    }
226
227    /// Set the shift
228    pub fn with_shift(mut self, shift: f64) -> Self {
229        self.shift = shift;
230        self
231    }
232
233    /// Set flags
234    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
235        self.flags = flags;
236        self
237    }
238
239    /// Set common item flags for this plot item (applies to all plot types)
240    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
241        self.item_flags = flags;
242        self
243    }
244
245    /// Make bars horizontal
246    pub fn horizontal(mut self) -> Self {
247        self.flags |= BarGroupsFlags::HORIZONTAL;
248        self
249    }
250
251    /// Stack bars
252    pub fn stacked(mut self) -> Self {
253        self.flags |= BarGroupsFlags::STACKED;
254        self
255    }
256
257    /// Validate the plot data
258    pub fn validate(&self) -> Result<(), PlotError> {
259        if self.label_ids.len() != self.item_count {
260            return Err(PlotError::InvalidData(format!(
261                "Label count ({}) must match item count ({})",
262                self.label_ids.len(),
263                self.item_count
264            )));
265        }
266
267        let expected_values = self
268            .item_count
269            .checked_mul(self.group_count)
270            .ok_or_else(|| {
271                PlotError::InvalidData("item_count * group_count overflowed usize".to_string())
272            })?;
273        if self.values.len() != expected_values {
274            return Err(PlotError::InvalidData(format!(
275                "Values length ({}) must equal item_count * group_count ({})",
276                self.values.len(),
277                expected_values
278            )));
279        }
280
281        if self.item_count == 0 || self.group_count == 0 {
282            return Err(PlotError::EmptyData);
283        }
284
285        let _ = i32::try_from(self.item_count).map_err(|_| {
286            PlotError::InvalidData("item_count exceeded ImPlot's i32 range".to_string())
287        })?;
288        let _ = i32::try_from(self.group_count).map_err(|_| {
289            PlotError::InvalidData("group_count exceeded ImPlot's i32 range".to_string())
290        })?;
291
292        Ok(())
293    }
294
295    /// Plot the bar groups
296    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
297        if self.validate().is_err() {
298            return;
299        }
300        let Ok(item_count) = i32::try_from(self.item_count) else {
301            return;
302        };
303        let Ok(group_count) = i32::try_from(self.group_count) else {
304            return;
305        };
306        let _guard = plot_ui.bind();
307        with_plot_str_slice(&self.label_ids, |label_ptrs| unsafe {
308            let spec = plot_spec_with_style(
309                self.style,
310                self.flags.bits() | self.item_flags.bits(),
311                PlotDataLayout::DEFAULT,
312            );
313            sys::ImPlot_PlotBarGroups_FloatPtr(
314                label_ptrs.as_ptr(),
315                self.values.as_ptr(),
316                item_count,
317                group_count,
318                self.group_size,
319                self.shift,
320                spec,
321            );
322        })
323    }
324}
325
326impl<'a> PlotData for BarGroupsPlotF32<'a> {
327    fn label(&self) -> &str {
328        "BarGroups" // Generic label for groups
329    }
330
331    fn data_len(&self) -> usize {
332        self.values.len()
333    }
334}
335
336/// Simple bar groups plot with automatic layout
337pub struct SimpleBarGroupsPlot<'a> {
338    labels: Vec<&'a str>,
339    data: Vec<Vec<f64>>,
340    style: PlotItemStyle,
341    group_size: f64,
342    flags: BarGroupsFlags,
343    item_flags: ItemFlags,
344}
345
346impl<'a> super::PlotItemStyled for SimpleBarGroupsPlot<'a> {
347    fn style_mut(&mut self) -> &mut PlotItemStyle {
348        &mut self.style
349    }
350}
351
352impl<'a> SimpleBarGroupsPlot<'a> {
353    /// Create a simple bar groups plot from a 2D data structure
354    ///
355    /// # Arguments
356    /// * `labels` - Labels for each series
357    /// * `data` - Vector of vectors, where each inner vector is a series
358    pub fn new(labels: Vec<&'a str>, data: Vec<Vec<f64>>) -> Self {
359        Self {
360            labels,
361            data,
362            style: PlotItemStyle::default(),
363            group_size: 0.67,
364            flags: BarGroupsFlags::NONE,
365            item_flags: ItemFlags::NONE,
366        }
367    }
368
369    /// Set ImPlotSpec-backed style overrides for this bar groups plot.
370    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
371        self.style = style;
372        self
373    }
374
375    /// Set the group size
376    pub fn with_group_size(mut self, group_size: f64) -> Self {
377        self.group_size = group_size;
378        self
379    }
380
381    /// Set flags
382    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
383        self.flags = flags;
384        self
385    }
386
387    /// Set common item flags for this plot item (applies to all plot types)
388    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
389        self.item_flags = flags;
390        self
391    }
392
393    /// Make bars horizontal
394    pub fn horizontal(mut self) -> Self {
395        self.flags |= BarGroupsFlags::HORIZONTAL;
396        self
397    }
398
399    /// Stack bars
400    pub fn stacked(mut self) -> Self {
401        self.flags |= BarGroupsFlags::STACKED;
402        self
403    }
404
405    /// Plot the bar groups
406    pub fn plot(self, plot_ui: &crate::PlotUi<'_>) {
407        if self.data.is_empty() || self.labels.is_empty() {
408            return;
409        }
410
411        let item_count = self.data.len();
412        let group_count = self.data[0].len();
413
414        // Flatten data into row-major order
415        let mut flattened_data = Vec::with_capacity(item_count * group_count);
416        for group_idx in 0..group_count {
417            for item_idx in 0..item_count {
418                if group_idx < self.data[item_idx].len() {
419                    flattened_data.push(self.data[item_idx][group_idx]);
420                } else {
421                    flattened_data.push(0.0); // Fill missing data with zeros
422                }
423            }
424        }
425
426        let plot = BarGroupsPlot::new(self.labels, &flattened_data, item_count, group_count)
427            .with_style(self.style)
428            .with_group_size(self.group_size)
429            .with_flags(self.flags)
430            .with_item_flags(self.item_flags);
431
432        plot.plot(plot_ui);
433    }
434}