Skip to main content

dear_implot/plots/
pie.rs

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