Skip to main content

dear_implot/plots/
pie.rs

1//! Pie chart plot implementation
2
3use super::{Plot, PlotError, PlotItemStyle, plot_spec_with_style, with_plot_str_slice_with_opt};
4use crate::{ItemFlags, PieChartFlags, sys};
5
6/// Builder for pie chart plots
7pub struct PieChartPlot<'a> {
8    label_ids: Vec<&'a str>,
9    values: &'a [f64],
10    style: PlotItemStyle,
11    center_x: f64,
12    center_y: f64,
13    radius: f64,
14    label_fmt: Option<&'a str>,
15    angle0: f64,
16    flags: PieChartFlags,
17    item_flags: ItemFlags,
18}
19
20impl<'a> super::PlotItemStyled for PieChartPlot<'a> {
21    fn style_mut(&mut self) -> &mut PlotItemStyle {
22        &mut self.style
23    }
24}
25
26impl<'a> PieChartPlot<'a> {
27    /// Create a new pie chart plot
28    ///
29    /// # Arguments
30    /// * `label_ids` - Labels for each slice of the pie
31    /// * `values` - Values for each slice
32    /// * `center_x` - X coordinate of the pie center in plot units
33    /// * `center_y` - Y coordinate of the pie center in plot units
34    /// * `radius` - Radius of the pie in plot units
35    pub fn new(
36        label_ids: Vec<&'a str>,
37        values: &'a [f64],
38        center_x: f64,
39        center_y: f64,
40        radius: f64,
41    ) -> Self {
42        Self {
43            label_ids,
44            values,
45            style: PlotItemStyle::default(),
46            center_x,
47            center_y,
48            radius,
49            label_fmt: Some("%.1f"),
50            angle0: 90.0, // Start angle in degrees
51            flags: PieChartFlags::NONE,
52            item_flags: ItemFlags::NONE,
53        }
54    }
55
56    /// Set the label format for slice values (e.g., "%.1f", "%.0f%%")
57    /// Set to None to disable labels
58    pub fn with_label_format(mut self, fmt: Option<&'a str>) -> Self {
59        self.label_fmt = fmt;
60        self
61    }
62
63    /// Set the starting angle in degrees (default: 90.0)
64    pub fn with_start_angle(mut self, angle: f64) -> Self {
65        self.angle0 = angle;
66        self
67    }
68
69    /// Set pie chart flags for customization
70    pub fn with_flags(mut self, flags: PieChartFlags) -> Self {
71        self.flags = flags;
72        self
73    }
74
75    /// Set common item flags for this plot item (applies to all plot types)
76    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
77        self.item_flags = flags;
78        self
79    }
80
81    /// Normalize the pie chart values (force full circle even if sum < 1.0)
82    pub fn normalize(mut self) -> Self {
83        self.flags |= PieChartFlags::NORMALIZE;
84        self
85    }
86
87    /// Ignore hidden slices when drawing (as if they were not there)
88    pub fn ignore_hidden(mut self) -> Self {
89        self.flags |= PieChartFlags::IGNORE_HIDDEN;
90        self
91    }
92
93    /// Enable exploding effect for legend-hovered slices
94    pub fn exploding(mut self) -> Self {
95        self.flags |= PieChartFlags::EXPLODING;
96        self
97    }
98
99    /// Draw slices without the per-slice border stroke.
100    pub fn no_slice_border(mut self) -> Self {
101        self.flags |= PieChartFlags::NO_SLICE_BORDER;
102        self
103    }
104
105    /// Validate the plot data
106    pub fn validate(&self) -> Result<(), PlotError> {
107        if self.values.is_empty() {
108            return Err(PlotError::EmptyData);
109        }
110
111        if self.label_ids.len() != self.values.len() {
112            return Err(PlotError::DataLengthMismatch {
113                x_len: self.label_ids.len(),
114                y_len: self.values.len(),
115            });
116        }
117
118        if self.radius <= 0.0 {
119            return Err(PlotError::InvalidData(
120                "Radius must be positive".to_string(),
121            ));
122        }
123
124        // Check for negative values
125        if self.values.iter().any(|&v| v < 0.0) {
126            return Err(PlotError::InvalidData(
127                "Pie chart values cannot be negative".to_string(),
128            ));
129        }
130
131        Ok(())
132    }
133}
134
135impl<'a> Plot for PieChartPlot<'a> {
136    fn plot(&self) {
137        if self.validate().is_err() {
138            return;
139        }
140        let Ok(count) = i32::try_from(self.values.len()) else {
141            return;
142        };
143        with_plot_str_slice_with_opt(
144            &self.label_ids,
145            self.label_fmt,
146            |label_ptrs, label_fmt_ptr| unsafe {
147                let spec = plot_spec_with_style(
148                    self.style,
149                    self.flags.bits() | self.item_flags.bits(),
150                    0,
151                    crate::IMPLOT_AUTO,
152                );
153                sys::ImPlot_PlotPieChart_doublePtrStr(
154                    label_ptrs.as_ptr(),
155                    self.values.as_ptr(),
156                    count,
157                    self.center_x,
158                    self.center_y,
159                    self.radius,
160                    label_fmt_ptr,
161                    self.angle0,
162                    spec,
163                );
164            },
165        )
166    }
167
168    fn label(&self) -> &str {
169        "PieChart" // Pie charts don't have a single label
170    }
171}
172
173/// Float version of pie chart for better performance with f32 data
174pub struct PieChartPlotF32<'a> {
175    label_ids: Vec<&'a str>,
176    values: &'a [f32],
177    style: PlotItemStyle,
178    center_x: f64,
179    center_y: f64,
180    radius: f64,
181    label_fmt: Option<&'a str>,
182    angle0: f64,
183    flags: PieChartFlags,
184    item_flags: ItemFlags,
185}
186
187impl<'a> super::PlotItemStyled for PieChartPlotF32<'a> {
188    fn style_mut(&mut self) -> &mut PlotItemStyle {
189        &mut self.style
190    }
191}
192
193impl<'a> PieChartPlotF32<'a> {
194    /// Create a new f32 pie chart plot
195    pub fn new(
196        label_ids: Vec<&'a str>,
197        values: &'a [f32],
198        center_x: f64,
199        center_y: f64,
200        radius: f64,
201    ) -> Self {
202        Self {
203            label_ids,
204            values,
205            style: PlotItemStyle::default(),
206            center_x,
207            center_y,
208            radius,
209            label_fmt: Some("%.1f"),
210            angle0: 90.0,
211            flags: PieChartFlags::NONE,
212            item_flags: ItemFlags::NONE,
213        }
214    }
215
216    /// Set the label format for slice values
217    pub fn with_label_format(mut self, fmt: Option<&'a str>) -> Self {
218        self.label_fmt = fmt;
219        self
220    }
221
222    /// Set the starting angle in degrees
223    pub fn with_start_angle(mut self, angle: f64) -> Self {
224        self.angle0 = angle;
225        self
226    }
227
228    /// Set pie chart flags for customization
229    pub fn with_flags(mut self, flags: PieChartFlags) -> Self {
230        self.flags = flags;
231        self
232    }
233
234    /// Set common item flags for this plot item (applies to all plot types)
235    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
236        self.item_flags = flags;
237        self
238    }
239
240    /// Normalize the pie chart values
241    pub fn normalize(mut self) -> Self {
242        self.flags |= PieChartFlags::NORMALIZE;
243        self
244    }
245
246    /// Ignore hidden slices when drawing
247    pub fn ignore_hidden(mut self) -> Self {
248        self.flags |= PieChartFlags::IGNORE_HIDDEN;
249        self
250    }
251
252    /// Enable exploding effect for legend-hovered slices
253    pub fn exploding(mut self) -> Self {
254        self.flags |= PieChartFlags::EXPLODING;
255        self
256    }
257
258    /// Draw slices without the per-slice border stroke.
259    pub fn no_slice_border(mut self) -> Self {
260        self.flags |= PieChartFlags::NO_SLICE_BORDER;
261        self
262    }
263
264    /// Validate the plot data
265    pub fn validate(&self) -> Result<(), PlotError> {
266        if self.values.is_empty() {
267            return Err(PlotError::EmptyData);
268        }
269
270        if self.label_ids.len() != self.values.len() {
271            return Err(PlotError::DataLengthMismatch {
272                x_len: self.label_ids.len(),
273                y_len: self.values.len(),
274            });
275        }
276
277        if self.radius <= 0.0 {
278            return Err(PlotError::InvalidData(
279                "Radius must be positive".to_string(),
280            ));
281        }
282
283        if self.values.iter().any(|&v| v < 0.0) {
284            return Err(PlotError::InvalidData(
285                "Pie chart values cannot be negative".to_string(),
286            ));
287        }
288
289        Ok(())
290    }
291}
292
293impl<'a> Plot for PieChartPlotF32<'a> {
294    fn plot(&self) {
295        if self.validate().is_err() {
296            return;
297        }
298        let Ok(count) = i32::try_from(self.values.len()) else {
299            return;
300        };
301        with_plot_str_slice_with_opt(
302            &self.label_ids,
303            self.label_fmt,
304            |label_ptrs, label_fmt_ptr| unsafe {
305                let spec = plot_spec_with_style(
306                    self.style,
307                    self.flags.bits() | self.item_flags.bits(),
308                    0,
309                    crate::IMPLOT_AUTO,
310                );
311                sys::ImPlot_PlotPieChart_FloatPtrStr(
312                    label_ptrs.as_ptr(),
313                    self.values.as_ptr(),
314                    count,
315                    self.center_x,
316                    self.center_y,
317                    self.radius,
318                    label_fmt_ptr,
319                    self.angle0,
320                    spec,
321                );
322            },
323        )
324    }
325
326    fn label(&self) -> &str {
327        "PieChart"
328    }
329}
330
331/// Convenience functions for quick pie chart plotting
332impl<'ui> crate::PlotUi<'ui> {
333    /// Plot a pie chart with f64 data
334    pub fn pie_chart_plot(
335        &self,
336        label_ids: Vec<&str>,
337        values: &[f64],
338        center_x: f64,
339        center_y: f64,
340        radius: f64,
341    ) -> Result<(), PlotError> {
342        let plot = PieChartPlot::new(label_ids, values, center_x, center_y, radius);
343        plot.validate()?;
344        plot.plot();
345        Ok(())
346    }
347
348    /// Plot a pie chart with f32 data
349    pub fn pie_chart_plot_f32(
350        &self,
351        label_ids: Vec<&str>,
352        values: &[f32],
353        center_x: f64,
354        center_y: f64,
355        radius: f64,
356    ) -> Result<(), PlotError> {
357        let plot = PieChartPlotF32::new(label_ids, values, center_x, center_y, radius);
358        plot.validate()?;
359        plot.plot();
360        Ok(())
361    }
362
363    /// Plot a centered pie chart (center at 0.5, 0.5 with radius 0.4)
364    pub fn centered_pie_chart(
365        &self,
366        label_ids: Vec<&str>,
367        values: &[f64],
368    ) -> Result<(), PlotError> {
369        let plot = PieChartPlot::new(label_ids, values, 0.5, 0.5, 0.4);
370        plot.validate()?;
371        plot.plot();
372        Ok(())
373    }
374}