dear-implot 0.12.0

High-level Rust bindings to ImPlot with dear-imgui-rs integration
Documentation
//! Pie chart plot implementation

use super::{Plot, PlotError, PlotItemStyle, plot_spec_with_style, with_plot_str_slice_with_opt};
use crate::{ItemFlags, PieChartFlags, sys};

/// Builder for pie chart plots
pub struct PieChartPlot<'a> {
    label_ids: Vec<&'a str>,
    values: &'a [f64],
    style: PlotItemStyle,
    center_x: f64,
    center_y: f64,
    radius: f64,
    label_fmt: Option<&'a str>,
    angle0: f64,
    flags: PieChartFlags,
    item_flags: ItemFlags,
}

impl<'a> super::PlotItemStyled for PieChartPlot<'a> {
    fn style_mut(&mut self) -> &mut PlotItemStyle {
        &mut self.style
    }
}

impl<'a> PieChartPlot<'a> {
    /// Create a new pie chart plot
    ///
    /// # Arguments
    /// * `label_ids` - Labels for each slice of the pie
    /// * `values` - Values for each slice
    /// * `center_x` - X coordinate of the pie center in plot units
    /// * `center_y` - Y coordinate of the pie center in plot units
    /// * `radius` - Radius of the pie in plot units
    pub fn new(
        label_ids: Vec<&'a str>,
        values: &'a [f64],
        center_x: f64,
        center_y: f64,
        radius: f64,
    ) -> Self {
        Self {
            label_ids,
            values,
            style: PlotItemStyle::default(),
            center_x,
            center_y,
            radius,
            label_fmt: Some("%.1f"),
            angle0: 90.0, // Start angle in degrees
            flags: PieChartFlags::NONE,
            item_flags: ItemFlags::NONE,
        }
    }

    /// Set the label format for slice values (e.g., "%.1f", "%.0f%%")
    /// Set to None to disable labels
    pub fn with_label_format(mut self, fmt: Option<&'a str>) -> Self {
        self.label_fmt = fmt;
        self
    }

    /// Set the starting angle in degrees (default: 90.0)
    pub fn with_start_angle(mut self, angle: f64) -> Self {
        self.angle0 = angle;
        self
    }

    /// Set pie chart flags for customization
    pub fn with_flags(mut self, flags: PieChartFlags) -> Self {
        self.flags = flags;
        self
    }

    /// Set common item flags for this plot item (applies to all plot types)
    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
        self.item_flags = flags;
        self
    }

    /// Normalize the pie chart values (force full circle even if sum < 1.0)
    pub fn normalize(mut self) -> Self {
        self.flags |= PieChartFlags::NORMALIZE;
        self
    }

    /// Ignore hidden slices when drawing (as if they were not there)
    pub fn ignore_hidden(mut self) -> Self {
        self.flags |= PieChartFlags::IGNORE_HIDDEN;
        self
    }

    /// Enable exploding effect for legend-hovered slices
    pub fn exploding(mut self) -> Self {
        self.flags |= PieChartFlags::EXPLODING;
        self
    }

    /// Draw slices without the per-slice border stroke.
    pub fn no_slice_border(mut self) -> Self {
        self.flags |= PieChartFlags::NO_SLICE_BORDER;
        self
    }

    /// Validate the plot data
    pub fn validate(&self) -> Result<(), PlotError> {
        if self.values.is_empty() {
            return Err(PlotError::EmptyData);
        }

        if self.label_ids.len() != self.values.len() {
            return Err(PlotError::DataLengthMismatch {
                x_len: self.label_ids.len(),
                y_len: self.values.len(),
            });
        }

        if self.radius <= 0.0 {
            return Err(PlotError::InvalidData(
                "Radius must be positive".to_string(),
            ));
        }

        // Check for negative values
        if self.values.iter().any(|&v| v < 0.0) {
            return Err(PlotError::InvalidData(
                "Pie chart values cannot be negative".to_string(),
            ));
        }

        Ok(())
    }
}

impl<'a> Plot for PieChartPlot<'a> {
    fn plot(&self) {
        if self.validate().is_err() {
            return;
        }
        let Ok(count) = i32::try_from(self.values.len()) else {
            return;
        };
        with_plot_str_slice_with_opt(
            &self.label_ids,
            self.label_fmt,
            |label_ptrs, label_fmt_ptr| unsafe {
                let spec = plot_spec_with_style(
                    self.style,
                    self.flags.bits() | self.item_flags.bits(),
                    0,
                    crate::IMPLOT_AUTO,
                );
                sys::ImPlot_PlotPieChart_doublePtrStr(
                    label_ptrs.as_ptr(),
                    self.values.as_ptr(),
                    count,
                    self.center_x,
                    self.center_y,
                    self.radius,
                    label_fmt_ptr,
                    self.angle0,
                    spec,
                );
            },
        )
    }

    fn label(&self) -> &str {
        "PieChart" // Pie charts don't have a single label
    }
}

/// Float version of pie chart for better performance with f32 data
pub struct PieChartPlotF32<'a> {
    label_ids: Vec<&'a str>,
    values: &'a [f32],
    style: PlotItemStyle,
    center_x: f64,
    center_y: f64,
    radius: f64,
    label_fmt: Option<&'a str>,
    angle0: f64,
    flags: PieChartFlags,
    item_flags: ItemFlags,
}

impl<'a> super::PlotItemStyled for PieChartPlotF32<'a> {
    fn style_mut(&mut self) -> &mut PlotItemStyle {
        &mut self.style
    }
}

impl<'a> PieChartPlotF32<'a> {
    /// Create a new f32 pie chart plot
    pub fn new(
        label_ids: Vec<&'a str>,
        values: &'a [f32],
        center_x: f64,
        center_y: f64,
        radius: f64,
    ) -> Self {
        Self {
            label_ids,
            values,
            style: PlotItemStyle::default(),
            center_x,
            center_y,
            radius,
            label_fmt: Some("%.1f"),
            angle0: 90.0,
            flags: PieChartFlags::NONE,
            item_flags: ItemFlags::NONE,
        }
    }

    /// Set the label format for slice values
    pub fn with_label_format(mut self, fmt: Option<&'a str>) -> Self {
        self.label_fmt = fmt;
        self
    }

    /// Set the starting angle in degrees
    pub fn with_start_angle(mut self, angle: f64) -> Self {
        self.angle0 = angle;
        self
    }

    /// Set pie chart flags for customization
    pub fn with_flags(mut self, flags: PieChartFlags) -> Self {
        self.flags = flags;
        self
    }

    /// Set common item flags for this plot item (applies to all plot types)
    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
        self.item_flags = flags;
        self
    }

    /// Normalize the pie chart values
    pub fn normalize(mut self) -> Self {
        self.flags |= PieChartFlags::NORMALIZE;
        self
    }

    /// Ignore hidden slices when drawing
    pub fn ignore_hidden(mut self) -> Self {
        self.flags |= PieChartFlags::IGNORE_HIDDEN;
        self
    }

    /// Enable exploding effect for legend-hovered slices
    pub fn exploding(mut self) -> Self {
        self.flags |= PieChartFlags::EXPLODING;
        self
    }

    /// Draw slices without the per-slice border stroke.
    pub fn no_slice_border(mut self) -> Self {
        self.flags |= PieChartFlags::NO_SLICE_BORDER;
        self
    }

    /// Validate the plot data
    pub fn validate(&self) -> Result<(), PlotError> {
        if self.values.is_empty() {
            return Err(PlotError::EmptyData);
        }

        if self.label_ids.len() != self.values.len() {
            return Err(PlotError::DataLengthMismatch {
                x_len: self.label_ids.len(),
                y_len: self.values.len(),
            });
        }

        if self.radius <= 0.0 {
            return Err(PlotError::InvalidData(
                "Radius must be positive".to_string(),
            ));
        }

        if self.values.iter().any(|&v| v < 0.0) {
            return Err(PlotError::InvalidData(
                "Pie chart values cannot be negative".to_string(),
            ));
        }

        Ok(())
    }
}

impl<'a> Plot for PieChartPlotF32<'a> {
    fn plot(&self) {
        if self.validate().is_err() {
            return;
        }
        let Ok(count) = i32::try_from(self.values.len()) else {
            return;
        };
        with_plot_str_slice_with_opt(
            &self.label_ids,
            self.label_fmt,
            |label_ptrs, label_fmt_ptr| unsafe {
                let spec = plot_spec_with_style(
                    self.style,
                    self.flags.bits() | self.item_flags.bits(),
                    0,
                    crate::IMPLOT_AUTO,
                );
                sys::ImPlot_PlotPieChart_FloatPtrStr(
                    label_ptrs.as_ptr(),
                    self.values.as_ptr(),
                    count,
                    self.center_x,
                    self.center_y,
                    self.radius,
                    label_fmt_ptr,
                    self.angle0,
                    spec,
                );
            },
        )
    }

    fn label(&self) -> &str {
        "PieChart"
    }
}

/// Convenience functions for quick pie chart plotting
impl<'ui> crate::PlotUi<'ui> {
    /// Plot a pie chart with f64 data
    pub fn pie_chart_plot(
        &self,
        label_ids: Vec<&str>,
        values: &[f64],
        center_x: f64,
        center_y: f64,
        radius: f64,
    ) -> Result<(), PlotError> {
        let plot = PieChartPlot::new(label_ids, values, center_x, center_y, radius);
        plot.validate()?;
        plot.plot();
        Ok(())
    }

    /// Plot a pie chart with f32 data
    pub fn pie_chart_plot_f32(
        &self,
        label_ids: Vec<&str>,
        values: &[f32],
        center_x: f64,
        center_y: f64,
        radius: f64,
    ) -> Result<(), PlotError> {
        let plot = PieChartPlotF32::new(label_ids, values, center_x, center_y, radius);
        plot.validate()?;
        plot.plot();
        Ok(())
    }

    /// Plot a centered pie chart (center at 0.5, 0.5 with radius 0.4)
    pub fn centered_pie_chart(
        &self,
        label_ids: Vec<&str>,
        values: &[f64],
    ) -> Result<(), PlotError> {
        let plot = PieChartPlot::new(label_ids, values, 0.5, 0.5, 0.4);
        plot.validate()?;
        plot.plot();
        Ok(())
    }
}