From 03e79685a121098f943e2a0f9f78e55607f5417c Mon Sep 17 00:00:00 2001 From: alceal Date: Sat, 4 Jan 2025 21:12:41 +0100 Subject: [PATCH] feat: Add PieChart support for visualizing categorical data --- src/lib.rs | 1 + src/plots/mod.rs | 1 + src/plots/piechart.rs | 155 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/plots/piechart.rs diff --git a/src/lib.rs b/src/lib.rs index 400843d..780a658 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ pub use crate::plots::heatmap::HeatMap; pub use crate::plots::histogram::Histogram; pub use crate::plots::image::Image; pub use crate::plots::lineplot::LinePlot; +pub use crate::plots::piechart::PieChart; pub use crate::plots::scatter3dplot::Scatter3dPlot; pub use crate::plots::scatterplot::ScatterPlot; pub use crate::plots::timeseriesplot::TimeSeriesPlot; diff --git a/src/plots/mod.rs b/src/plots/mod.rs index 7f67b42..ba736c5 100644 --- a/src/plots/mod.rs +++ b/src/plots/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod heatmap; pub(crate) mod histogram; pub(crate) mod image; pub(crate) mod lineplot; +pub(crate) mod piechart; pub(crate) mod scatter3dplot; pub(crate) mod scatterplot; pub(crate) mod timeseriesplot; diff --git a/src/plots/piechart.rs b/src/plots/piechart.rs new file mode 100644 index 0000000..da3496d --- /dev/null +++ b/src/plots/piechart.rs @@ -0,0 +1,155 @@ +use bon::bon; + +use plotly::{Layout as LayoutPlotly, Pie, Trace}; + +use polars::frame::DataFrame; +use serde::Serialize; + +use crate::{ + common::{Layout, PlotHelper, Polar}, + components::Text, +}; + +/// A structure representing a pie chart. +/// +/// The `PieChart` struct allows for the creation and customization of pie charts, supporting +/// features such as labels, hole size for donut-style charts, slice pulling, rotation, and customizable plot titles. +/// It is ideal for visualizing proportions and distributions in categorical data. +/// +/// # Arguments +/// +/// * `data` - A reference to the `DataFrame` containing the data to be plotted. +/// * `labels` - A string slice specifying the column name to be used for slice labels. +/// * `hole` - An optional `f64` value specifying the size of the hole in the center of the pie chart. +/// A value of `0.0` creates a full pie chart, while a value closer to `1.0` creates a thinner ring. +/// * `pull` - An optional `f64` value specifying the fraction by which each slice should be pulled out from the center. +/// * `rotation` - An optional `f64` value specifying the starting angle (in degrees) of the first slice. +/// * `plot_title` - An optional `Text` struct specifying the title of the plot. +/// +/// # Example +/// +/// ## Basic Pie Chart with Customization +/// +/// ```rust +/// use plotlars::{PieChart, Plot, Text}; +/// +/// let dataset = LazyCsvReader::new("data/penguins.csv") +/// .finish() +/// .unwrap() +/// .select([ +/// col("species"), +/// ]) +/// .collect() +/// .unwrap(); +/// +/// PieChart::builder() +/// .data(&dataset) +/// .labels("species") +/// .hole(0.4) // Creates a donut-style chart +/// .pull(0.01) // Slightly separates each slice +/// .rotation(20.0) // Rotates the chart by 20 degrees +/// .plot_title( +/// Text::from("Pie Chart") +/// .font("Arial") +/// .size(18) +/// ) +/// .build() +/// .plot(); +/// ``` +/// +/// ![Example](https://imgur.com/jE70hYS.png) +#[derive(Clone, Serialize)] +pub struct PieChart { + traces: Vec>, + layout: LayoutPlotly, +} + +#[bon] +impl PieChart { + #[builder(on(String, into), on(Text, into))] + pub fn new( + data: &DataFrame, + labels: &str, + hole: Option, + pull: Option, + rotation: Option, + plot_title: Option, + ) -> Self { + let x_title = None; + let y_title = None; + let z_title = None; + let legend_title = None; + let x_axis = None; + let y_axis = None; + let z_axis = None; + let legend = None; + + let layout = Self::create_layout( + plot_title, + x_title, + y_title, + z_title, + legend_title, + x_axis, + y_axis, + z_axis, + legend, + ); + + let mut traces = vec![]; + + let trace = Self::create_trace(data, labels, hole, pull, rotation); + + traces.push(trace); + + Self { traces, layout } + } + + fn create_trace( + data: &DataFrame, + labels: &str, + hole: Option, + pull: Option, + rotation: Option, + ) -> Box { + let labels = Self::get_string_column(data, labels) + .iter() + .filter_map(|s| { + if s.is_some() { + Some(s.clone().unwrap().to_owned()) + } else { + None + } + }) + .collect::>(); + + let mut trace = Pie::::from_labels(&labels); + + if let Some(hole) = hole { + trace = trace.hole(hole); + } + + if let Some(pull) = pull { + trace = trace.pull(pull); + } + + if let Some(rotation) = rotation { + trace = trace.rotation(rotation); + } + + trace + } +} + +impl Layout for PieChart {} +impl Polar for PieChart {} + +impl PlotHelper for PieChart { + fn get_layout(&self) -> &LayoutPlotly { + &self.layout + } + + fn get_traces(&self) -> &Vec> { + &self.traces + } +}