dear_implot/plots/
bar_groups.rs

1//! Bar groups plot implementation
2
3use super::{PlotData, PlotError, safe_cstring};
4use crate::BarGroupsFlags;
5use crate::sys;
6use std::ffi::CString;
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    item_count: usize,
13    group_count: usize,
14    group_size: f64,
15    shift: f64,
16    flags: BarGroupsFlags,
17}
18
19impl<'a> BarGroupsPlot<'a> {
20    /// Create a new bar groups plot
21    ///
22    /// # Arguments
23    /// * `label_ids` - Labels for each item in the group
24    /// * `values` - Values in row-major order (item_count rows, group_count cols)
25    /// * `item_count` - Number of items (series) in each group
26    /// * `group_count` - Number of groups
27    pub fn new(
28        label_ids: Vec<&'a str>,
29        values: &'a [f64],
30        item_count: usize,
31        group_count: usize,
32    ) -> Self {
33        Self {
34            label_ids,
35            values,
36            item_count,
37            group_count,
38            group_size: 0.67,
39            shift: 0.0,
40            flags: BarGroupsFlags::NONE,
41        }
42    }
43
44    /// Set the group size (width of each group)
45    pub fn with_group_size(mut self, group_size: f64) -> Self {
46        self.group_size = group_size;
47        self
48    }
49
50    /// Set the shift (horizontal offset)
51    pub fn with_shift(mut self, shift: f64) -> Self {
52        self.shift = shift;
53        self
54    }
55
56    /// Set bar groups flags for customization
57    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
58        self.flags = flags;
59        self
60    }
61
62    /// Make bars horizontal instead of vertical
63    pub fn horizontal(mut self) -> Self {
64        self.flags |= BarGroupsFlags::HORIZONTAL;
65        self
66    }
67
68    /// Stack bars instead of grouping them side by side
69    pub fn stacked(mut self) -> Self {
70        self.flags |= BarGroupsFlags::STACKED;
71        self
72    }
73
74    /// Validate the plot data
75    pub fn validate(&self) -> Result<(), PlotError> {
76        if self.label_ids.len() != self.item_count {
77            return Err(PlotError::InvalidData(format!(
78                "Label count ({}) must match item count ({})",
79                self.label_ids.len(),
80                self.item_count
81            )));
82        }
83
84        let expected_values = self.item_count * self.group_count;
85        if self.values.len() != expected_values {
86            return Err(PlotError::InvalidData(format!(
87                "Values length ({}) must equal item_count * group_count ({})",
88                self.values.len(),
89                expected_values
90            )));
91        }
92
93        if self.item_count == 0 || self.group_count == 0 {
94            return Err(PlotError::EmptyData);
95        }
96
97        Ok(())
98    }
99
100    /// Plot the bar groups
101    pub fn plot(self) {
102        // Convert labels to CString pointers
103        let label_cstrings: Vec<CString> = self
104            .label_ids
105            .iter()
106            .map(|&label| safe_cstring(label))
107            .collect();
108
109        let label_ptrs: Vec<*const i8> = label_cstrings.iter().map(|cstr| cstr.as_ptr()).collect();
110
111        unsafe {
112            sys::ImPlot_PlotBarGroups_doublePtr(
113                label_ptrs.as_ptr(),
114                self.values.as_ptr(),
115                self.item_count as i32,
116                self.group_count as i32,
117                self.group_size,
118                self.shift,
119                self.flags.bits() as i32,
120            );
121        }
122    }
123}
124
125impl<'a> PlotData for BarGroupsPlot<'a> {
126    fn label(&self) -> &str {
127        "BarGroups" // Generic label for groups
128    }
129
130    fn data_len(&self) -> usize {
131        self.values.len()
132    }
133}
134
135/// Bar groups plot for f32 data
136pub struct BarGroupsPlotF32<'a> {
137    label_ids: Vec<&'a str>,
138    values: &'a [f32],
139    item_count: usize,
140    group_count: usize,
141    group_size: f64,
142    shift: f64,
143    flags: BarGroupsFlags,
144}
145
146impl<'a> BarGroupsPlotF32<'a> {
147    /// Create a new bar groups plot with f32 data
148    pub fn new(
149        label_ids: Vec<&'a str>,
150        values: &'a [f32],
151        item_count: usize,
152        group_count: usize,
153    ) -> Self {
154        Self {
155            label_ids,
156            values,
157            item_count,
158            group_count,
159            group_size: 0.67,
160            shift: 0.0,
161            flags: BarGroupsFlags::NONE,
162        }
163    }
164
165    /// Set the group size
166    pub fn with_group_size(mut self, group_size: f64) -> Self {
167        self.group_size = group_size;
168        self
169    }
170
171    /// Set the shift
172    pub fn with_shift(mut self, shift: f64) -> Self {
173        self.shift = shift;
174        self
175    }
176
177    /// Set flags
178    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
179        self.flags = flags;
180        self
181    }
182
183    /// Make bars horizontal
184    pub fn horizontal(mut self) -> Self {
185        self.flags |= BarGroupsFlags::HORIZONTAL;
186        self
187    }
188
189    /// Stack bars
190    pub fn stacked(mut self) -> Self {
191        self.flags |= BarGroupsFlags::STACKED;
192        self
193    }
194
195    /// Validate the plot data
196    pub fn validate(&self) -> Result<(), PlotError> {
197        if self.label_ids.len() != self.item_count {
198            return Err(PlotError::InvalidData(format!(
199                "Label count ({}) must match item count ({})",
200                self.label_ids.len(),
201                self.item_count
202            )));
203        }
204
205        let expected_values = self.item_count * self.group_count;
206        if self.values.len() != expected_values {
207            return Err(PlotError::InvalidData(format!(
208                "Values length ({}) must equal item_count * group_count ({})",
209                self.values.len(),
210                expected_values
211            )));
212        }
213
214        if self.item_count == 0 || self.group_count == 0 {
215            return Err(PlotError::EmptyData);
216        }
217
218        Ok(())
219    }
220
221    /// Plot the bar groups
222    pub fn plot(self) {
223        // Convert labels to CString pointers
224        let label_cstrings: Vec<CString> = self
225            .label_ids
226            .iter()
227            .map(|&label| safe_cstring(label))
228            .collect();
229
230        let label_ptrs: Vec<*const i8> = label_cstrings.iter().map(|cstr| cstr.as_ptr()).collect();
231
232        unsafe {
233            sys::ImPlot_PlotBarGroups_FloatPtr(
234                label_ptrs.as_ptr(),
235                self.values.as_ptr(),
236                self.item_count as i32,
237                self.group_count as i32,
238                self.group_size,
239                self.shift,
240                self.flags.bits() as i32,
241            );
242        }
243    }
244}
245
246impl<'a> PlotData for BarGroupsPlotF32<'a> {
247    fn label(&self) -> &str {
248        "BarGroups" // Generic label for groups
249    }
250
251    fn data_len(&self) -> usize {
252        self.values.len()
253    }
254}
255
256/// Simple bar groups plot with automatic layout
257pub struct SimpleBarGroupsPlot<'a> {
258    labels: Vec<&'a str>,
259    data: Vec<Vec<f64>>,
260    group_size: f64,
261    flags: BarGroupsFlags,
262}
263
264impl<'a> SimpleBarGroupsPlot<'a> {
265    /// Create a simple bar groups plot from a 2D data structure
266    ///
267    /// # Arguments
268    /// * `labels` - Labels for each series
269    /// * `data` - Vector of vectors, where each inner vector is a series
270    pub fn new(labels: Vec<&'a str>, data: Vec<Vec<f64>>) -> Self {
271        Self {
272            labels,
273            data,
274            group_size: 0.67,
275            flags: BarGroupsFlags::NONE,
276        }
277    }
278
279    /// Set the group size
280    pub fn with_group_size(mut self, group_size: f64) -> Self {
281        self.group_size = group_size;
282        self
283    }
284
285    /// Set flags
286    pub fn with_flags(mut self, flags: BarGroupsFlags) -> Self {
287        self.flags = flags;
288        self
289    }
290
291    /// Make bars horizontal
292    pub fn horizontal(mut self) -> Self {
293        self.flags |= BarGroupsFlags::HORIZONTAL;
294        self
295    }
296
297    /// Stack bars
298    pub fn stacked(mut self) -> Self {
299        self.flags |= BarGroupsFlags::STACKED;
300        self
301    }
302
303    /// Plot the bar groups
304    pub fn plot(self) {
305        if self.data.is_empty() || self.labels.is_empty() {
306            return;
307        }
308
309        let item_count = self.data.len();
310        let group_count = self.data[0].len();
311
312        // Flatten data into row-major order
313        let mut flattened_data = Vec::with_capacity(item_count * group_count);
314        for group_idx in 0..group_count {
315            for item_idx in 0..item_count {
316                if group_idx < self.data[item_idx].len() {
317                    flattened_data.push(self.data[item_idx][group_idx]);
318                } else {
319                    flattened_data.push(0.0); // Fill missing data with zeros
320                }
321            }
322        }
323
324        let plot = BarGroupsPlot::new(self.labels, &flattened_data, item_count, group_count)
325            .with_group_size(self.group_size)
326            .with_flags(self.flags);
327
328        plot.plot();
329    }
330}