refactor: dashboard usage

This commit is contained in:
jialin
2026-05-06 12:27:22 +08:00
committed by jialin
parent 705b33e307
commit 898c7f1be2
23 changed files with 901 additions and 35 deletions
+192
View File
@@ -0,0 +1,192 @@
import { Chart } from '@gpustack/core-ui';
import { formatLargeNumber } from '@gpustack/core-ui/utils';
import { Empty, Spin, theme } from 'antd';
import React, { useMemo, useRef } from 'react';
import { generateCoolColors } from '../bar-chart';
export interface PieChartItem {
name: string;
value: number;
}
interface PieChartProps {
data: PieChartItem[];
height?: number | string;
width?: number | string;
loading?: boolean;
colorOffset?: number;
total?: number;
totalLabel?: string;
}
const PieChart: React.FC<PieChartProps> = ({
data,
height = 300,
width = '100%',
loading = false,
colorOffset = 0,
total,
totalLabel
}) => {
const { token } = theme.useToken();
const chartRef = useRef<{ chart: any } | null>(null);
const colors = useMemo(() => {
const generatedColors = generateCoolColors(data.length + colorOffset);
return generatedColors.slice(colorOffset);
}, [colorOffset, data.length]);
const options = useMemo(
() => ({
color: colors,
tooltip: {
trigger: 'item',
backgroundColor: token.colorBgElevated,
borderColor: 'transparent',
formatter: (params: any) => {
return `<div class="tooltip-wrapper">
<span class="tooltip-item">
<span class="tooltip-item-name">
<span class="tooltip-item-dot" style="border-radius:50%;background-color:${params.color};"></span>
<span class="tooltip-item-title">${params.name}</span>:
</span>
<span class="tooltip-value">${formatLargeNumber(params.value)}</span>
</span>
</div>`;
}
},
legend: {
type: 'scroll',
orient: 'vertical',
right: 0,
top: 18,
bottom: 18,
itemWidth: 8,
itemHeight: 8,
itemGap: 12,
textStyle: {
color: token.colorTextTertiary,
overflow: 'truncate',
width: 180
},
pageTextStyle: { color: token.colorTextTertiary },
pageIconColor: token.colorTextTertiary,
pageIconInactiveColor: token.colorTextDisabled,
data: data.map((item) => item.name),
show: data.length > 0
},
series: [
{
type: 'pie',
radius: ['52%', '72%'],
center: ['34%', '50%'],
avoidLabelOverlap: true,
label: {
show: false
},
emphasis: {
label: {
show: true,
formatter: (params: any) => `${params.percent}%`,
fontSize: 16,
fontWeight: 600,
color: token.colorText
}
},
labelLine: {
show: false
},
data
}
]
}),
[colors, data, token]
);
if (!data.length) {
return (
<div
style={{
width,
height,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
{loading ? (
<Spin size="middle" />
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</div>
);
}
const displayTotal = total ?? data.reduce((sum, item) => sum + item.value, 0);
return (
<div style={{ width, height, position: 'relative' }}>
<Chart
ref={chartRef as any}
options={options as any}
height={height}
width={width}
/>
<div
style={{
position: 'absolute',
left: '34%',
top: '50%',
transform: 'translate(-50%, -50%)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
textAlign: 'center'
}}
>
<span
style={{
fontSize: 20,
fontWeight: 600,
color: token.colorText,
lineHeight: 1.2
}}
>
{formatLargeNumber(displayTotal)}
</span>
{totalLabel && (
<span
style={{
fontSize: 12,
color: token.colorTextTertiary,
marginTop: 4
}}
>
{totalLabel}
</span>
)}
</div>
{loading && (
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--ant-color-bg-container)',
opacity: 0.6,
pointerEvents: 'none'
}}
>
<Spin size="middle" />
</div>
)}
</div>
);
};
export default PieChart;