use ab_glyph::{FontRef, PxScale};
use imageproc::drawing::text_size;
use std::f64::consts::PI;
use crate::figure::{
canvas::{pixelcanvas::PixelCanvas, svgcanvas::SvgCanvas},
configuration::figureconfig::FigureConfig,
figuretypes::piechart::PieChart,
};
use super::drawer::Drawer;
use std::any::Any;
impl Drawer for PieChart {
fn draw_svg(&mut self, svg_canvas: &mut SvgCanvas) {
let width = svg_canvas.width as f64;
let height = svg_canvas.height as f64;
let margin = svg_canvas.margin as f64;
let font_size = 12.0;
let cfg = &self.config;
let margin_bg_color = svg_canvas.background_color.clone();
svg_canvas.draw_rect(0.0, 0.0, width, height, &margin_bg_color, "black", 1.0, 1.0);
self.fill_svg_background(svg_canvas, cfg);
svg_canvas.draw_title(
width / 2.0,
margin / 2.0,
&self.title,
font_size * 2.0,
"black",
);
let total: f64 = self.datasets.iter().map(|dataset| dataset.1).sum();
let cx = width / 2.0;
let cy = height / 2.0;
let radius = (width.min(height) - 2.0 * margin) / 2.0;
svg_canvas.elements.push(format!(
r#"<g transform="translate({cx:.2},{cy:.2})" stroke="black" stroke-width="1">"#
));
let mut start_angle = 0.0;
for dataset in &self.datasets {
let value_ratio = dataset.1 / total; let sweep_angle = value_ratio * 2.0 * std::f64::consts::PI; let end_angle = start_angle + sweep_angle;
let x1 = radius * start_angle.cos();
let y1 = radius * start_angle.sin();
let x2 = radius * end_angle.cos();
let y2 = radius * end_angle.sin();
let large_arc_flag = if sweep_angle > std::f64::consts::PI {
1
} else {
0
};
svg_canvas.elements.push(format!(
r#"<path d="M 0 0 L {:.2} {:.2} A {:.2} {:.2} 0 {} 1 {:.2} {:.2} Z" fill="rgb({},{},{})"/>"#,
x1, y1, radius, radius, large_arc_flag, x2, y2,
dataset.2[0], dataset.2[1], dataset.2[2]
));
let mid_angle = start_angle + sweep_angle / 2.0;
let label_x = (radius * 0.6) * mid_angle.cos(); let label_y = (radius * 0.6) * mid_angle.sin();
svg_canvas.elements.push(format!(
r#"<text x="{:.2}" y="{:.2}" font-size="{:.2}" fill="black" text-anchor="middle" alignment-baseline="middle">{:.1}%</text>"#,
label_x, label_y, font_size, value_ratio * 100.0
));
start_angle = end_angle;
}
svg_canvas.elements.push("</g>".to_string());
let legend_x_start = margin + 10.0; let legend_y = height - margin + font_size * 1.5 + 10.0;
let mut legend_x = legend_x_start;
let mut elements = String::new();
let legend_bg_color = svg_canvas.background_color.clone();
for dataset in &self.datasets {
elements.push_str(&format!(
r#"<rect x="{:.2}" y="{:.2}" width="{:.2}" height="{:.2}" fill="rgb({},{},{})"/>"#,
legend_x, legend_y, font_size, font_size, dataset.2[0], dataset.2[1], dataset.2[2]
));
elements.push_str(&format!(
r#"<text x="{:.2}" y="{:.2}" font-size="{:.2}" fill="black">{}</text>"#,
legend_x + font_size * 1.3,
legend_y + font_size - 2.0,
font_size,
dataset.0
));
legend_x += font_size * 5.0 + dataset.0.len() as f64 * font_size * 0.6;
}
svg_canvas.draw_rect(
legend_x_start - 5.0,
legend_y - 5.0,
legend_x - legend_x_start + 5.0,
font_size + 10.0,
&legend_bg_color,
"black",
0.5,
0.5,
);
svg_canvas.elements.push(elements);
}
fn draw(&mut self, canvas: &mut PixelCanvas) {
canvas.clear();
let cfg = &self.config;
self.fill_background(canvas, cfg);
let margin = canvas.margin;
let width = canvas.width;
let height = canvas.height;
self.draw_title(canvas, cfg, width / 2, margin / 2, &self.title);
let total: f64 = self.datasets.iter().map(|(_, value, _)| value).sum();
if total == 0.0 {
return;
}
let center_x = width / 2;
let center_y = height / 2;
let radius = (width.min(height) / 2 - margin) as i32;
let mut start_angle = 0.0;
for (_label, value, color) in &self.datasets {
let percentage = value / total;
let sweep_angle = 2.0 * PI * percentage;
self.draw_slice(
canvas,
center_x as i32,
center_y as i32,
radius,
start_angle,
start_angle + sweep_angle,
*color,
);
let mid_angle = start_angle + sweep_angle / 2.0;
let label_x = center_x as f64 + (radius as f64 * 0.6 * mid_angle.cos());
let label_y = center_y as f64 - (radius as f64 * 0.6 * mid_angle.sin());
self.draw_label(
canvas,
cfg,
label_x as u32,
label_y as u32,
&format!("{:.1}%", percentage * 100.0),
);
start_angle += sweep_angle;
}
self.draw_legend(canvas);
}
fn draw_legend(&self, canvas: &mut PixelCanvas) {
let font_path = self
.config
.font_label
.as_ref()
.expect("Font path is not set");
let font_bytes = std::fs::read(font_path).expect("Failed to read font file");
let font = FontRef::try_from_slice(&font_bytes).unwrap();
let scale = PxScale { x: 10.0, y: 10.0 };
let square_size = 10; let padding = 5; let line_height = 20; let legend_margin = canvas.margin;
let mut x = canvas.margin;
let mut y = canvas.height - legend_margin;
for dataset in &self.datasets {
let (w, h) = text_size(scale, &font, &dataset.0);
for dy in 0..square_size {
for dx in 0..square_size {
canvas.draw_pixel(
x + dx,
y + square_size * 2 + dy + h, dataset.2,
);
}
}
let text_x: u32 = x + square_size + padding;
canvas.draw_text(
text_x,
y + 2 * square_size + h,
&dataset.0,
dataset.2,
&font,
scale,
);
x += square_size + padding + w + padding;
if x > canvas.width - canvas.margin {
x = canvas.margin;
y -= line_height;
}
}
}
fn as_any(&mut self) -> &mut (dyn Any + 'static) {
self as &mut dyn Any
}
fn get_figure_config(&self) -> &FigureConfig {
&self.config
}
}