dear_implot/plots/
pie.rs

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