import { getCommonOptions, mergeEchartWidgetConfig, type EchartOptionsProps, } from '../echart' import type { PieConfig, PieWidgetConfig, PieWidgetData } from './types' import { buildGridConfig, buildLegendConfig, flattenObjectArrayToCSV, createTooltipPositioner, createTooltipFormatter, createChartDownloadConfig, niceNum, buildSeriesLabelConfig, } from '../utils/chart-config' export const pieDownloadConfig = createChartDownloadConfig( flattenObjectArrayToCSV, ) /** * Generates ECharts configuration for pie and donut chart widgets. Falls back to a horizontal bar layout when multiple data series are provided. * * @param props - Pie chart configuration including data and theme. * @returns Widget config with ECharts option object. */ export function pieConfig(props: PieConfig): PieWidgetConfig { return { type: 'pie', option: mergeEchartWidgetConfig(getCommonOptions(props), getOption(props)), formatter: props.formatter, labelFormatter: props.labelFormatter, } } function getOption({ data = [], theme, formatter, labelFormatter, }: PieConfig): EchartOptionsProps { const multiSeries = (data?.length ?? 0) > 1 if (multiSeries) { let niceMin = 0 let niceMax = 1 return { legend: buildLegendConfig({ hasLegend: true, labelFormatter }), grid: { ...buildGridConfig(true, theme), right: parseInt(theme.spacing(4)), }, xAxis: { type: 'value', min: (extent: { min: number }) => { niceMin = extent.min < 0 ? niceNum(extent.min) : 0 return niceMin }, max: (extent: { min: number; max: number }) => { niceMax = extent.max <= 0 ? 1 : niceNum(extent.max) return niceMax }, axisLabel: { fontSize: theme.typography.overlineDelicate.fontSize, fontFamily: theme.typography.overlineDelicate.fontFamily, margin: parseInt(theme.spacing(1)), show: true, showMaxLabel: true, showMinLabel: true, verticalAlign: 'bottom' as const, inside: true, formatter: (value: number) => { if (value !== niceMax && value !== niceMin) return '' if (value === 0) return '' return formatter ? formatter(value) : String(value) }, }, axisLine: { show: false, }, axisTick: { show: false, }, splitLine: { show: true, lineStyle: { color: theme.palette.black[4], }, }, }, yAxis: { type: 'category', axisLine: { show: false, }, axisTick: { show: false, }, axisLabel: { padding: [parseInt(theme.spacing(0.5)), 0, 0, 0], ...(labelFormatter && { formatter: (value: string | number) => String(labelFormatter(value)), }), }, }, tooltip: { position: createTooltipPositioner(theme), formatter: createTooltipFormatter((item) => { const value = item.value as Record const index = item.dimensionNames?.[item.encode?.x?.at(0) ?? 1] const _value = value[index ?? ''] const formattedValue = typeof _value === 'number' && formatter ? formatter(_value) : (_value ?? '') const marker = typeof item.marker === 'string' ? item.marker : '' const seriesName = item.seriesName ? `${item.seriesName}: ` : '' const name = labelFormatter ? String(labelFormatter(item.name ?? '')) : (item.name ?? '') return { name, seriesName, marker, value: formattedValue } }), }, series: data.map((_: unknown, index: number) => ({ datasetIndex: index, type: 'bar', barMaxWidth: 100, emphasis: { focus: 'series', }, ...buildSeriesLabelConfig(formatter, 'x'), })), } } const hasLegend = true return { legend: buildLegendConfig({ hasLegend, labelFormatter }), grid: { ...buildGridConfig(hasLegend, theme), left: 0, top: 0, right: 0, }, xAxis: { show: false, }, yAxis: { show: false, }, tooltip: { trigger: 'item', formatter: createTooltipFormatter((item) => { const value = item.value && typeof item.value === 'object' && !Array.isArray(item.value) ? (Object.values(item.value) as (string | number)[]) : [] const index = item.encode?.value?.at(0) const _value = value[index ?? 0] const formattedValue = typeof _value === 'number' && formatter ? formatter(_value) : (_value ?? '') const marker = typeof item.marker === 'string' ? item.marker : '' const seriesName = item.seriesName ? `${item.seriesName}: ` : '' const name = labelFormatter ? String(labelFormatter(item.name ?? '')) : (item.name ?? '') return { name: seriesName, seriesName: name, marker, value: formattedValue, } }), }, color: Object.values(theme.palette.qualitative.bold), series: data.map((_: unknown, index: number) => ({ datasetIndex: index, type: 'pie', colorBy: 'data', radius: ['74%', '90%'], avoidLabelOverlap: true, selectedOffset: 0, bottom: parseInt(theme.spacing(4)), label: { show: true, position: 'center', formatter: (params) => { const { name } = params const encodeIndex = params.encode?.value?.[0] if (encodeIndex === undefined) { return '' } const value = Object.values(params.data ?? {}).at(encodeIndex) as | number | string const formattedValue = typeof value === 'number' && formatter ? formatter(value) : (value ?? '') const nameFormatted = labelFormatter ? String(labelFormatter(name ?? '')) : (name ?? '') return `{c|${formattedValue}}\n\n{b|${nameFormatted}}` }, rich: { b: { fontSize: 16, fontWeight: 'normal', lineHeight: 20, }, c: { fontSize: 28, fontWeight: 'bold', lineHeight: 27, }, }, }, emphasis: { disabled: true, }, itemStyle: { borderColor: theme.palette.background.paper, borderWidth: 1, }, })), } as EchartOptionsProps }