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, plot_ui: &crate::PlotUi<'_>) {
140        if self.validate().is_err() {
141            return;
142        }
143        let Ok(count) = i32::try_from(self.values.len()) else {
144            return;
145        };
146        let _guard = plot_ui.bind();
147        with_plot_str_slice_with_opt(
148            &self.label_ids,
149            self.label_fmt,
150            |label_ptrs, label_fmt_ptr| unsafe {
151                let spec = plot_spec_with_style(
152                    self.style,
153                    self.flags.bits() | self.item_flags.bits(),
154                    PlotDataLayout::DEFAULT,
155                );
156                sys::ImPlot_PlotPieChart_doublePtrStr(
157                    label_ptrs.as_ptr(),
158                    self.values.as_ptr(),
159                    count,
160                    self.center_x,
161                    self.center_y,
162                    self.radius,
163                    label_fmt_ptr,
164                    self.angle0,
165                    spec,
166                );
167            },
168        )
169    }
170
171    fn label(&self) -> &str {
172        "PieChart" // Pie charts don't have a single label
173    }
174}
175
176/// Float version of pie chart for better performance with f32 data
177pub struct PieChartPlotF32<'a> {
178    label_ids: Vec<&'a str>,
179    values: &'a [f32],
180    style: PlotItemStyle,
181    center_x: f64,
182    center_y: f64,
183    radius: f64,
184    label_fmt: Option<&'a str>,
185    angle0: f64,
186    flags: PieChartFlags,
187    item_flags: ItemFlags,
188}
189
190impl<'a> super::PlotItemStyled for PieChartPlotF32<'a> {
191    fn style_mut(&mut self) -> &mut PlotItemStyle {
192        &mut self.style
193    }
194}
195
196impl<'a> PieChartPlotF32<'a> {
197    /// Create a new f32 pie chart plot
198    pub fn new(
199        label_ids: Vec<&'a str>,
200        values: &'a [f32],
201        center_x: f64,
202        center_y: f64,
203        radius: f64,
204    ) -> Self {
205        Self {
206            label_ids,
207            values,
208            style: PlotItemStyle::default(),
209            center_x,
210            center_y,
211            radius,
212            label_fmt: Some("%.1f"),
213            angle0: 90.0,
214            flags: PieChartFlags::NONE,
215            item_flags: ItemFlags::NONE,
216        }
217    }
218
219    /// Set the label format for slice values
220    pub fn with_label_format(mut self, fmt: Option<&'a str>) -> Self {
221        self.label_fmt = fmt;
222        self
223    }
224
225    /// Set the starting angle in degrees
226    pub fn with_start_angle(mut self, angle: f64) -> Self {
227        self.angle0 = angle;
228        self
229    }
230
231    /// Set pie chart flags for customization
232    pub fn with_flags(mut self, flags: PieChartFlags) -> Self {
233        self.flags = flags;
234        self
235    }
236
237    /// Set common item flags for this plot item (applies to all plot types)
238    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
239        self.item_flags = flags;
240        self
241    }
242
243    /// Normalize the pie chart values
244    pub fn normalize(mut self) -> Self {
245        self.flags |= PieChartFlags::NORMALIZE;
246        self
247    }
248
249    /// Ignore hidden slices when drawing
250    pub fn ignore_hidden(mut self) -> Self {
251        self.flags |= PieChartFlags::IGNORE_HIDDEN;
252        self
253    }
254
255    /// Enable exploding effect for legend-hovered slices
256    pub fn exploding(mut self) -> Self {
257        self.flags |= PieChartFlags::EXPLODING;
258        self
259    }
260
261    /// Draw slices without the per-slice border stroke.
262    pub fn no_slice_border(mut self) -> Self {
263        self.flags |= PieChartFlags::NO_SLICE_BORDER;
264        self
265    }
266
267    /// Validate the plot data
268    pub fn validate(&self) -> Result<(), PlotError> {
269        if self.values.is_empty() {
270            return Err(PlotError::EmptyData);
271        }
272
273        if self.label_ids.len() != self.values.len() {
274            return Err(PlotError::DataLengthMismatch {
275                x_len: self.label_ids.len(),
276                y_len: self.values.len(),
277            });
278        }
279
280        if self.radius <= 0.0 {
281            return Err(PlotError::InvalidData(
282                "Radius must be positive".to_string(),
283            ));
284        }
285
286        if self.values.iter().any(|&v| v < 0.0) {
287            return Err(PlotError::InvalidData(
288                "Pie chart values cannot be negative".to_string(),
289            ));
290        }
291
292        Ok(())
293    }
294}
295
296impl<'a> Plot for PieChartPlotF32<'a> {
297    fn plot(&self, plot_ui: &crate::PlotUi<'_>) {
298        if self.validate().is_err() {
299            return;
300        }
301        let Ok(count) = i32::try_from(self.values.len()) else {
302            return;
303        };
304        let _guard = plot_ui.bind();
305        with_plot_str_slice_with_opt(
306            &self.label_ids,
307            self.label_fmt,
308            |label_ptrs, label_fmt_ptr| unsafe {
309                let spec = plot_spec_with_style(
310                    self.style,
311                    self.flags.bits() | self.item_flags.bits(),
312                    PlotDataLayout::DEFAULT,
313                );
314                sys::ImPlot_PlotPieChart_FloatPtrStr(
315                    label_ptrs.as_ptr(),
316                    self.values.as_ptr(),
317                    count,
318                    self.center_x,
319                    self.center_y,
320                    self.radius,
321                    label_fmt_ptr,
322                    self.angle0,
323                    spec,
324                );
325            },
326        )
327    }
328
329    fn label(&self) -> &str {
330        "PieChart"
331    }
332}
333
334/// Convenience functions for quick pie chart plotting
335impl<'ui> crate::PlotUi<'ui> {
336    /// Plot a pie chart with f64 data
337    pub fn pie_chart_plot(
338        &self,
339        label_ids: Vec<&str>,
340        values: &[f64],
341        center_x: f64,
342        center_y: f64,
343        radius: f64,
344    ) -> Result<(), PlotError> {
345        let plot = PieChartPlot::new(label_ids, values, center_x, center_y, radius);
346        plot.validate()?;
347        plot.plot(self);
348        Ok(())
349    }
350
351    /// Plot a pie chart with f32 data
352    pub fn pie_chart_plot_f32(
353        &self,
354        label_ids: Vec<&str>,
355        values: &[f32],
356        center_x: f64,
357        center_y: f64,
358        radius: f64,
359    ) -> Result<(), PlotError> {
360        let plot = PieChartPlotF32::new(label_ids, values, center_x, center_y, radius);
361        plot.validate()?;
362        plot.plot(self);
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        plot.plot(self);
375        Ok(())
376    }
377}