1use super::*;
4use crate::model::ChartDataPoint;
5
6pub struct PieChartConfig {
8 pub donut: bool,
9 pub show_legend: bool,
10 pub title: Option<String>,
11}
12
13pub fn build(
15 width: f64,
16 height: f64,
17 data: &[ChartDataPoint],
18 config: &PieChartConfig,
19) -> Vec<ChartPrimitive> {
20 if data.is_empty() {
21 return vec![];
22 }
23
24 let mut primitives = Vec::new();
25
26 let title_offset = if config.title.is_some() {
27 TITLE_HEIGHT
28 } else {
29 0.0
30 };
31
32 let legend_width = if config.show_legend { 80.0 } else { 0.0 };
34
35 let available_w = width - legend_width;
36 let available_h = height - title_offset;
37 let radius = (available_w.min(available_h) / 2.0 - LABEL_MARGIN).max(1.0);
38 let cx = available_w / 2.0;
39 let cy = title_offset + available_h / 2.0;
40
41 let total: f64 = data.iter().map(|d| d.value).sum();
42 if total <= 0.0 {
43 return vec![];
44 }
45
46 let mut start_angle: f64 = -std::f64::consts::FRAC_PI_2; for (i, dp) in data.iter().enumerate() {
49 let slice_angle = (dp.value / total) * std::f64::consts::TAU;
50 let end_angle = start_angle + slice_angle;
51 let color = resolve_color(dp.color.as_deref(), i);
52
53 primitives.push(ChartPrimitive::ArcSector {
54 cx,
55 cy,
56 r: radius,
57 start_angle,
58 end_angle,
59 fill: color,
60 });
61
62 start_angle = end_angle;
63 }
64
65 if config.donut {
67 let inner_r = radius * 0.55;
68 primitives.push(ChartPrimitive::Circle {
69 cx,
70 cy,
71 r: inner_r,
72 fill: Color::WHITE,
73 });
74 }
75
76 if config.show_legend {
78 let legend_x = available_w + LABEL_MARGIN;
79 let legend_y_start = title_offset + LABEL_MARGIN;
80 let swatch_size = 8.0;
81 let line_height = 14.0;
82
83 for (i, dp) in data.iter().enumerate() {
84 let ly = legend_y_start + i as f64 * line_height;
85 let color = resolve_color(dp.color.as_deref(), i);
86
87 primitives.push(ChartPrimitive::Rect {
88 x: legend_x,
89 y: ly,
90 w: swatch_size,
91 h: swatch_size,
92 fill: color,
93 });
94 primitives.push(ChartPrimitive::Label {
95 text: dp.label.clone(),
96 x: legend_x + swatch_size + LABEL_MARGIN,
97 y: ly + swatch_size - 1.0,
98 font_size: AXIS_LABEL_FONT,
99 color: LABEL_COLOR,
100 anchor: TextAnchor::Left,
101 });
102 }
103 }
104
105 if let Some(ref title) = config.title {
107 primitives.push(ChartPrimitive::Label {
108 text: title.clone(),
109 x: width / 2.0,
110 y: TITLE_FONT,
111 font_size: TITLE_FONT,
112 color: Color::BLACK,
113 anchor: TextAnchor::Center,
114 });
115 }
116
117 primitives
118}