Skip to main content

dear_implot/plots/
pie.rs

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