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.item_count * self.group_count;
107        if self.values.len() != expected_values {
108            return Err(PlotError::InvalidData(format!(
109                "Values length ({}) must equal item_count * group_count ({})",
110                self.values.len(),
111                expected_values
112            )));
113        }
114
115        if self.item_count == 0 || self.group_count == 0 {
116            return Err(PlotError::EmptyData);
117        }
118
119        Ok(())
120    }
121
122    /// Plot the bar groups
123    pub fn plot(self) {
124        with_plot_str_slice(&self.label_ids, |label_ptrs| unsafe {
125            let spec = plot_spec_with_style(
126                self.style,
127                self.flags.bits() | self.item_flags.bits(),
128                PlotDataLayout::DEFAULT,
129            );
130            sys::ImPlot_PlotBarGroups_doublePtr(
131                label_ptrs.as_ptr(),
132                self.values.as_ptr(),
133                self.item_count as i32,
134                self.group_count as i32,
135                self.group_size,
136                self.shift,
137                spec,
138            );
139        })
140    }
141}
142
143impl<'a> PlotData for BarGroupsPlot<'a> {
144    fn label(&self) -> &str {
145        "BarGroups" // Generic label for groups
146    }
147
148    fn data_len(&self) -> usize {
149        self.values.len()
150    }
151}
152
153/// Bar groups plot for f32 data
154pub struct BarGroupsPlotF32<'a> {
155    label_ids: Vec<&'a str>,
156    values: &'a [f32],
157    style: PlotItemStyle,
158    item_count: usize,
159    group_count: usize,
160    group_size: f64,
161    shift: f64,
162    flags: BarGroupsFlags,
163    item_flags: ItemFlags,
164}
165
166impl<'a> super::PlotItemStyled for BarGroupsPlotF32<'a> {
167    fn style_mut(&mut self) -> &mut PlotItemStyle {
168        &mut self.style
169    }
170}
171
172impl<'a> BarGroupsPlotF32<'a> {
173    /// Create a new bar groups plot with f32 data
174    pub fn new(
175        label_ids: Vec<&'a str>,
176        values: &'a [f32],
177        item_count: usize,
178        group_count: usize,
179    ) -> Self {
180        Self {
181            label_ids,
182            values,
183            style: PlotItemStyle::default(),
184            item_count,
185            group_count,
186            group_size: 0.67,
187            shift: 0.0,
188            flags: BarGroupsFlags::NONE,
189            item_flags: ItemFlags::NONE,
190        }
191    }
192
193    /// Set the group size
194    pub fn with_group_size(mut self, group_size: f64) -> Self {
195        self.group_size = group_size;
196        self
197    }
198
199    /// Set ImPlotSpec-backed style overrides for this bar groups plot.
200    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
201        self.style = style;
202        self
203    }
204
205    /// Set the shift
206    pub fn with_shift(mut self, shift: f64) -> Self {
207        self.shift = shift;
208        self
209    }
210
211    /// Set flags
212    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
213        self.flags = flags;
214        self
215    }
216
217    /// Set common item flags for this plot item (applies to all plot types)
218    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
219        self.item_flags = flags;
220        self
221    }
222
223    /// Make bars horizontal
224    pub fn horizontal(mut self) -> Self {
225        self.flags |= BarGroupsFlags::HORIZONTAL;
226        self
227    }
228
229    /// Stack bars
230    pub fn stacked(mut self) -> Self {
231        self.flags |= BarGroupsFlags::STACKED;
232        self
233    }
234
235    /// Validate the plot data
236    pub fn validate(&self) -> Result<(), PlotError> {
237        if self.label_ids.len() != self.item_count {
238            return Err(PlotError::InvalidData(format!(
239                "Label count ({}) must match item count ({})",
240                self.label_ids.len(),
241                self.item_count
242            )));
243        }
244
245        let expected_values = self.item_count * self.group_count;
246        if self.values.len() != expected_values {
247            return Err(PlotError::InvalidData(format!(
248                "Values length ({}) must equal item_count * group_count ({})",
249                self.values.len(),
250                expected_values
251            )));
252        }
253
254        if self.item_count == 0 || self.group_count == 0 {
255            return Err(PlotError::EmptyData);
256        }
257
258        Ok(())
259    }
260
261    /// Plot the bar groups
262    pub fn plot(self) {
263        with_plot_str_slice(&self.label_ids, |label_ptrs| unsafe {
264            let spec = plot_spec_with_style(
265                self.style,
266                self.flags.bits() | self.item_flags.bits(),
267                PlotDataLayout::DEFAULT,
268            );
269            sys::ImPlot_PlotBarGroups_FloatPtr(
270                label_ptrs.as_ptr(),
271                self.values.as_ptr(),
272                self.item_count as i32,
273                self.group_count as i32,
274                self.group_size,
275                self.shift,
276                spec,
277            );
278        })
279    }
280}
281
282impl<'a> PlotData for BarGroupsPlotF32<'a> {
283    fn label(&self) -> &str {
284        "BarGroups" // Generic label for groups
285    }
286
287    fn data_len(&self) -> usize {
288        self.values.len()
289    }
290}
291
292/// Simple bar groups plot with automatic layout
293pub struct SimpleBarGroupsPlot<'a> {
294    labels: Vec<&'a str>,
295    data: Vec<Vec<f64>>,
296    style: PlotItemStyle,
297    group_size: f64,
298    flags: BarGroupsFlags,
299    item_flags: ItemFlags,
300}
301
302impl<'a> super::PlotItemStyled for SimpleBarGroupsPlot<'a> {
303    fn style_mut(&mut self) -> &mut PlotItemStyle {
304        &mut self.style
305    }
306}
307
308impl<'a> SimpleBarGroupsPlot<'a> {
309    /// Create a simple bar groups plot from a 2D data structure
310    ///
311    /// # Arguments
312    /// * `labels` - Labels for each series
313    /// * `data` - Vector of vectors, where each inner vector is a series
314    pub fn new(labels: Vec<&'a str>, data: Vec<Vec<f64>>) -> Self {
315        Self {
316            labels,
317            data,
318            style: PlotItemStyle::default(),
319            group_size: 0.67,
320            flags: BarGroupsFlags::NONE,
321            item_flags: ItemFlags::NONE,
322        }
323    }
324
325    /// Set ImPlotSpec-backed style overrides for this bar groups plot.
326    pub fn with_style(mut self, style: PlotItemStyle) -> Self {
327        self.style = style;
328        self
329    }
330
331    /// Set the group size
332    pub fn with_group_size(mut self, group_size: f64) -> Self {
333        self.group_size = group_size;
334        self
335    }
336
337    /// Set flags
338    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
339        self.flags = flags;
340        self
341    }
342
343    /// Set common item flags for this plot item (applies to all plot types)
344    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
345        self.item_flags = flags;
346        self
347    }
348
349    /// Make bars horizontal
350    pub fn horizontal(mut self) -> Self {
351        self.flags |= BarGroupsFlags::HORIZONTAL;
352        self
353    }
354
355    /// Stack bars
356    pub fn stacked(mut self) -> Self {
357        self.flags |= BarGroupsFlags::STACKED;
358        self
359    }
360
361    /// Plot the bar groups
362    pub fn plot(self) {
363        if self.data.is_empty() || self.labels.is_empty() {
364            return;
365        }
366
367        let item_count = self.data.len();
368        let group_count = self.data[0].len();
369
370        // Flatten data into row-major order
371        let mut flattened_data = Vec::with_capacity(item_count * group_count);
372        for group_idx in 0..group_count {
373            for item_idx in 0..item_count {
374                if group_idx < self.data[item_idx].len() {
375                    flattened_data.push(self.data[item_idx][group_idx]);
376                } else {
377                    flattened_data.push(0.0); // Fill missing data with zeros
378                }
379            }
380        }
381
382        let plot = BarGroupsPlot::new(self.labels, &flattened_data, item_count, group_count)
383            .with_style(self.style)
384            .with_group_size(self.group_size)
385            .with_flags(self.flags)
386            .with_item_flags(self.item_flags);
387
388        plot.plot();
389    }
390}