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