import { DEFAULT_CHART_OPTIONS } from "./options";
import type { PieChartMetrics } from "./types/PieChartMetrics";
import type {
PieChartOptions,
ResolvedPieChartOptions,
} from "./types/PieChartOptions";
import {
calculatePercentage,
calculateTotal,
polarToCartesian,
resolvePieChartOptions,
validatePieChartMetrics,
} from "./utils/PieChartUtil";
import { createLegend, createTitle } from "./utils/ChartUtil";
/**
* Default pie chart options
*/
export const DEFAULT_PIE_CHART_OPTIONS: PieChartOptions = {
...DEFAULT_CHART_OPTIONS,
doughnut: false,
doughnutRatio: 0.6, // Inner circle is 60% of outer circle
startAngle: 0,
showPercentages: true,
};
/**
* Creates the SVG segments for the pie chart
* @param metrics - The data for the pie chart
* @param config - Resolved pie chart options
* @returns SVG string for the pie segments
*/
const createPieSegments = (
metrics: PieChartMetrics,
config: ResolvedPieChartOptions,
): string => {
// Initialize SVG string
let svg = "";
// Calculate total and prepare for drawing
const total = calculateTotal(metrics);
const segments = Object.entries(metrics);
// Determine chart dimensions
const chartWidth = config.width - config.margin.left - config.margin.right;
const chartHeight = config.height - config.margin.top - config.margin.bottom;
const radius = Math.min(chartWidth, chartHeight) / 2;
const innerRadius = config.doughnut ? radius * config.doughnutRatio : 0;
const centerX = chartWidth / 2;
const centerY = chartHeight / 2;
// Start drawing from the specified angle
let currentAngle = config.startAngle;
// Draw each segment
segments.forEach(([label, value], index) => {
const percentage = calculatePercentage(value, total);
const angleSize = (percentage / 100) * 360;
const endAngle = currentAngle + angleSize;
const colorIndex = index % config.colors.length;
// Calculate arc path
const startOuter = polarToCartesian(centerX, centerY, radius, currentAngle);
const endOuter = polarToCartesian(centerX, centerY, radius, endAngle);
const startInner = polarToCartesian(
centerX,
centerY,
innerRadius,
currentAngle,
);
const endInner = polarToCartesian(centerX, centerY, innerRadius, endAngle);
// Determine if the arc is more than 180 degrees (for large-arc-flag)
const largeArcFlag = angleSize > 180 ? 1 : 0;
// Create path for the segment
let path: string;
if (config.doughnut) {
// Path for doughnut segment (with inner circle cutout)
path = [
`M ${startOuter.x} ${startOuter.y}`, // Move to start of outer arc
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endOuter.x} ${endOuter.y}`, // Draw outer arc
`L ${endInner.x} ${endInner.y}`, // Line to inner circle
`A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${startInner.x} ${startInner.y}`, // Draw inner arc (in opposite direction)
"Z", // Close the path
].join(" ");
} else {
// Path for regular pie segment
path = [
`M ${centerX} ${centerY}`, // Move to center
`L ${startOuter.x} ${startOuter.y}`, // Line to start of arc
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endOuter.x} ${endOuter.y}`, // Draw arc
"Z", // Close the path back to center
].join(" ");
}
// Add segment to SVG
svg += ` \n`;
// Calculate position for the percentage text
if (config.showValues || config.showPercentages) {
// Position text at middle of segment arc
const midAngle = currentAngle + angleSize / 2;
// Place text at 2/3 distance from center to edge for better visibility
const textRadius = config.doughnut
? innerRadius + (radius - innerRadius) / 2
: radius * 0.67;
const textPos = polarToCartesian(centerX, centerY, textRadius, midAngle);
// Only show percentage/value if the segment is large enough
if (angleSize > 10) {
const formattedValue = config.showPercentages
? `${Math.round(percentage)}%`
: config.formatValue(value);
svg += ` ${formattedValue}\n`;
}
}
// Update current angle for next segment
currentAngle = endAngle;
});
return svg;
};
/**
* Generates an SVG pie chart from given metrics
* @param metrics - The data for the pie chart
* @param options - Optional configuration for the chart
* @returns SVG string representing the pie chart
*/
export const generatePieChart = (
metrics: PieChartMetrics,
options: Partial = {},
): string => {
// Validate input
validatePieChartMetrics(metrics);
// Resolve options
const config = resolvePieChartOptions(options);
// Create the SVG content
let svg = `";
return svg;
};
/**
* Generates an SVG doughnut chart from given metrics
* This is a convenience function that sets the doughnut option to true
* @param metrics - The data for the doughnut chart
* @param options - Optional configuration for the chart
* @returns SVG string representing the doughnut chart
*/
export const generateDoughnutChart = (
metrics: PieChartMetrics,
options: Partial = {},
): string => {
return generatePieChart(metrics, { ...options, doughnut: true });
};