plotlars/plots/
piechart.rs

1use bon::bon;
2
3use plotly::{Layout as LayoutPlotly, Pie, Trace};
4
5use polars::frame::DataFrame;
6use serde::Serialize;
7
8use crate::{
9    common::{Layout, PlotHelper, Polar},
10    components::Text,
11};
12
13/// A structure representing a pie chart.
14///
15/// The `PieChart` struct allows for the creation and customization of pie charts, supporting
16/// features such as labels, hole size for donut-style charts, slice pulling, rotation, and customizable plot titles.
17/// It is ideal for visualizing proportions and distributions in categorical data.
18///
19/// # Arguments
20///
21/// * `data` - A reference to the `DataFrame` containing the data to be plotted.
22/// * `labels` - A string slice specifying the column name to be used for slice labels.
23/// * `hole` - An optional `f64` value specifying the size of the hole in the center of the pie chart.
24///   A value of `0.0` creates a full pie chart, while a value closer to `1.0` creates a thinner ring.
25/// * `pull` - An optional `f64` value specifying the fraction by which each slice should be pulled out from the center.
26/// * `rotation` - An optional `f64` value specifying the starting angle (in degrees) of the first slice.
27/// * `plot_title` - An optional `Text` struct specifying the title of the plot.
28///
29/// # Example
30///
31/// ## Basic Pie Chart with Customization
32///
33/// ```rust
34/// use polars::prelude::*;
35/// use plotlars::{PieChart, Plot, Text};
36///
37/// let dataset = LazyCsvReader::new(PlPath::new("data/penguins.csv"))
38///     .finish()
39///     .unwrap()
40///     .select([
41///         col("species"),
42///     ])
43///     .collect()
44///     .unwrap();
45///
46/// PieChart::builder()
47///     .data(&dataset)
48///     .labels("species")
49///     .hole(0.4)
50///     .pull(0.01)
51///     .rotation(20.0)
52///     .plot_title(
53///         Text::from("Pie Chart")
54///             .font("Arial")
55///             .size(18)
56///     )
57///     .build()
58///     .plot();
59/// ```
60///
61/// ![Example](https://imgur.com/jE70hYS.png)
62#[derive(Clone, Serialize)]
63pub struct PieChart {
64    traces: Vec<Box<dyn Trace + 'static>>,
65    layout: LayoutPlotly,
66}
67
68#[bon]
69impl PieChart {
70    #[builder(on(String, into), on(Text, into))]
71    pub fn new(
72        data: &DataFrame,
73        labels: &str,
74        hole: Option<f64>,
75        pull: Option<f64>,
76        rotation: Option<f64>,
77        plot_title: Option<Text>,
78    ) -> Self {
79        let x_title = None;
80        let y_title = None;
81        let z_title = None;
82        let legend_title = None;
83        let x_axis = None;
84        let y_axis = None;
85        let z_axis = None;
86        let legend = None;
87        let y2_title = None;
88        let y2_axis = None;
89
90        let layout = Self::create_layout(
91            plot_title,
92            x_title,
93            y_title,
94            y2_title,
95            z_title,
96            legend_title,
97            x_axis,
98            y_axis,
99            y2_axis,
100            z_axis,
101            legend,
102        );
103
104        let mut traces = vec![];
105
106        let trace = Self::create_trace(data, labels, hole, pull, rotation);
107
108        traces.push(trace);
109
110        Self { traces, layout }
111    }
112
113    fn create_trace(
114        data: &DataFrame,
115        labels: &str,
116        hole: Option<f64>,
117        pull: Option<f64>,
118        rotation: Option<f64>,
119    ) -> Box<dyn Trace + 'static> {
120        let labels = Self::get_string_column(data, labels)
121            .iter()
122            .filter_map(|s| {
123                if s.is_some() {
124                    Some(s.clone().unwrap().to_owned())
125                } else {
126                    None
127                }
128            })
129            .collect::<Vec<String>>();
130
131        let mut trace = Pie::<u32>::from_labels(&labels);
132
133        if let Some(hole) = hole {
134            trace = trace.hole(hole);
135        }
136
137        if let Some(pull) = pull {
138            trace = trace.pull(pull);
139        }
140
141        if let Some(rotation) = rotation {
142            trace = trace.rotation(rotation);
143        }
144
145        trace
146    }
147}
148
149impl Layout for PieChart {}
150impl Polar for PieChart {}
151
152impl PlotHelper for PieChart {
153    fn get_layout(&self) -> &LayoutPlotly {
154        &self.layout
155    }
156
157    fn get_traces(&self) -> &Vec<Box<dyn Trace + 'static>> {
158        &self.traces
159    }
160}