2026-04-04 11:05:01 +00:00
|
|
|
import {
|
|
|
|
|
customElement,
|
|
|
|
|
property,
|
|
|
|
|
type TemplateResult,
|
|
|
|
|
} from '@design.estate/dees-element';
|
|
|
|
|
|
|
|
|
|
import { DeesChartEchartsBase } from '../dees-chart-echarts-base.js';
|
|
|
|
|
import { demoFunc } from './demo.js';
|
|
|
|
|
import { donutStyles } from './styles.js';
|
|
|
|
|
import { renderChartDonut } from './template.js';
|
2026-04-04 12:29:39 +00:00
|
|
|
import { getEchartsSeriesColors, getThemeColors, hexToRgba } from '../dees-chart-echarts-theme.js';
|
2026-04-04 11:05:01 +00:00
|
|
|
|
|
|
|
|
export interface IDonutDataItem {
|
|
|
|
|
name: string;
|
|
|
|
|
value: number;
|
|
|
|
|
color?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
interface HTMLElementTagNameMap {
|
|
|
|
|
'dees-chart-donut': DeesChartDonut;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@customElement('dees-chart-donut')
|
|
|
|
|
export class DeesChartDonut extends DeesChartEchartsBase {
|
|
|
|
|
public static demo = demoFunc;
|
|
|
|
|
public static demoGroups = ['Chart'];
|
|
|
|
|
|
|
|
|
|
@property({ type: Array })
|
|
|
|
|
accessor data: IDonutDataItem[] = [];
|
|
|
|
|
|
|
|
|
|
@property({ type: Boolean })
|
|
|
|
|
accessor showLegend: boolean = true;
|
|
|
|
|
|
|
|
|
|
@property({ type: Boolean })
|
|
|
|
|
accessor showLabels: boolean = true;
|
|
|
|
|
|
|
|
|
|
@property({ type: String })
|
|
|
|
|
accessor innerRadiusPercent: string = '55%';
|
|
|
|
|
|
|
|
|
|
@property({ attribute: false })
|
|
|
|
|
accessor valueFormatter: (value: number) => string = (val) => `${val}`;
|
|
|
|
|
|
|
|
|
|
public static styles = donutStyles;
|
|
|
|
|
|
|
|
|
|
public render(): TemplateResult {
|
|
|
|
|
return renderChartDonut(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async updated(changedProperties: Map<string, any>) {
|
|
|
|
|
super.updated(changedProperties);
|
|
|
|
|
if (
|
|
|
|
|
this.chartInstance &&
|
|
|
|
|
(changedProperties.has('data') ||
|
|
|
|
|
changedProperties.has('showLegend') ||
|
|
|
|
|
changedProperties.has('showLabels') ||
|
|
|
|
|
changedProperties.has('innerRadiusPercent'))
|
|
|
|
|
) {
|
|
|
|
|
this.updateChart();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected buildOption(): Record<string, any> {
|
2026-04-04 12:29:39 +00:00
|
|
|
const themeColors = getThemeColors(this.goBright);
|
2026-04-04 11:05:01 +00:00
|
|
|
const seriesColors = getEchartsSeriesColors(this.goBright);
|
2026-04-04 12:29:39 +00:00
|
|
|
const fillAlpha = this.goBright ? 0.15 : 0.2;
|
|
|
|
|
|
|
|
|
|
const legendData: Array<{ name: string; itemStyle: { color: string } }> = [];
|
|
|
|
|
|
|
|
|
|
const data = this.data.map((item, index) => {
|
|
|
|
|
const color = item.color || seriesColors[index % seriesColors.length];
|
|
|
|
|
legendData.push({ name: item.name, itemStyle: { color } });
|
|
|
|
|
return {
|
|
|
|
|
name: item.name,
|
|
|
|
|
value: item.value,
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: hexToRgba(color, fillAlpha),
|
|
|
|
|
borderColor: color,
|
|
|
|
|
borderWidth: 1,
|
|
|
|
|
},
|
|
|
|
|
emphasis: {
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: hexToRgba(color, fillAlpha + 0.15),
|
|
|
|
|
borderColor: color,
|
|
|
|
|
borderWidth: 1.5,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
2026-04-04 11:05:01 +00:00
|
|
|
|
|
|
|
|
const formatter = this.valueFormatter;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
formatter: (params: any) => {
|
2026-04-04 12:29:39 +00:00
|
|
|
const solidColor = params.data?.itemStyle?.borderColor || params.color;
|
|
|
|
|
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${solidColor};"></span>`;
|
|
|
|
|
return `${marker}${params.name}: <strong>${formatter(params.value)}</strong> (${params.percent}%)`;
|
2026-04-04 11:05:01 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
legend: this.showLegend
|
|
|
|
|
? {
|
|
|
|
|
orient: 'vertical',
|
|
|
|
|
right: 16,
|
|
|
|
|
top: 'center',
|
|
|
|
|
itemWidth: 10,
|
|
|
|
|
itemHeight: 10,
|
|
|
|
|
itemGap: 12,
|
2026-04-04 12:29:39 +00:00
|
|
|
data: legendData,
|
2026-04-04 11:05:01 +00:00
|
|
|
formatter: (name: string) => {
|
|
|
|
|
const item = this.data.find((d) => d.name === name);
|
|
|
|
|
return item ? `${name} ${formatter(item.value)}` : name;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
: { show: false },
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
type: 'pie',
|
|
|
|
|
radius: [this.innerRadiusPercent, '85%'],
|
|
|
|
|
center: this.showLegend ? ['35%', '50%'] : ['50%', '50%'],
|
|
|
|
|
avoidLabelOverlap: true,
|
2026-04-04 12:29:39 +00:00
|
|
|
padAngle: 2,
|
2026-04-04 11:05:01 +00:00
|
|
|
itemStyle: {
|
|
|
|
|
borderRadius: 4,
|
|
|
|
|
},
|
|
|
|
|
label: this.showLabels
|
|
|
|
|
? {
|
|
|
|
|
show: true,
|
|
|
|
|
formatter: '{b}: {d}%',
|
|
|
|
|
fontSize: 11,
|
2026-04-04 12:29:39 +00:00
|
|
|
color: themeColors.textSecondary,
|
2026-04-04 11:25:19 +00:00
|
|
|
textBorderColor: 'transparent',
|
2026-04-04 11:05:01 +00:00
|
|
|
}
|
|
|
|
|
: { show: false },
|
|
|
|
|
data,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|