156 lines
4.3 KiB
React
156 lines
4.3 KiB
React
import { useRef, useEffect, useState } from 'react';
|
|
import * as echarts from 'echarts';
|
|
|
|
const chartData = [
|
|
{ value: 25, name: '校企益邦' },
|
|
{ value: 25, name: '科研管理系统' },
|
|
{ value: 25, name: '智慧党建平台' },
|
|
{ value: 25, name: '实训管理平台' },
|
|
];
|
|
|
|
const rotationNeeded = [45, -45, -135, -225];
|
|
|
|
function normalizeAngle(a) {
|
|
return ((a % 360) + 360) % 360;
|
|
}
|
|
|
|
function shortestRotation(from, to) {
|
|
const fromN = normalizeAngle(from);
|
|
const toN = normalizeAngle(to);
|
|
let diff = toN - fromN;
|
|
if (diff > 180) diff -= 360;
|
|
if (diff < -180) diff += 360;
|
|
return from + diff;
|
|
}
|
|
|
|
/** 将 hex 颜色转为带透明度的 rgba */
|
|
function hexToRgba(hex, alpha) {
|
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
return `rgba(${r},${g},${b},${alpha})`;
|
|
}
|
|
|
|
export default function RosePieChart({ onSelect, activeIndex, colors }) {
|
|
const containerRef = useRef(null);
|
|
const chartRef = useRef(null);
|
|
const rotationRef = useRef(0);
|
|
const [rotation, setRotation] = useState(0);
|
|
|
|
// 默认回退颜色
|
|
const safeColors = colors?.length === 4 ? colors : ['#60ce93', '#0c86cf', '#aa3130', '#8b50ee'];
|
|
|
|
useEffect(() => {
|
|
if (!containerRef.current) return;
|
|
|
|
chartRef.current = echarts.init(containerRef.current, null, { renderer: 'canvas' });
|
|
|
|
chartRef.current.setOption({
|
|
tooltip: {
|
|
trigger: 'item',
|
|
backgroundColor: 'rgba(10,22,40,0.95)',
|
|
borderColor: 'rgba(59,130,246,0.3)',
|
|
borderWidth: 1,
|
|
textStyle: { color: '#e2e8f0', fontSize: 13 },
|
|
formatter: '{b}',
|
|
},
|
|
series: [{
|
|
type: 'pie',
|
|
radius: ['25%', '75%'],
|
|
center: ['50%', '50%'],
|
|
startAngle: 90,
|
|
padAngle: 3,
|
|
itemStyle: { borderRadius: 6, borderWidth: 0 },
|
|
label: {
|
|
show: true,
|
|
position: 'inside',
|
|
color: '#e2e8f0',
|
|
fontSize: 15,
|
|
fontWeight: 'bold',
|
|
rotate: 'tangential',
|
|
formatter: '{b}',
|
|
},
|
|
emphasis: {
|
|
scaleSize: 12,
|
|
itemStyle: {
|
|
shadowBlur: 40,
|
|
shadowColor: 'rgba(59,130,246,0.5)',
|
|
borderColor: 'rgba(59,130,246,0.6)',
|
|
borderWidth: 0,
|
|
},
|
|
label: { fontSize: 14, fontWeight: 'bold', color: '#fff' },
|
|
},
|
|
data: chartData.map((d, i) => ({
|
|
...d,
|
|
itemStyle: { color: safeColors[i], opacity: 1, borderRadius: 6, borderWidth: 0 },
|
|
})),
|
|
animationType: 'scale',
|
|
animationEasing: 'elasticOut',
|
|
animationDuration: 1200,
|
|
}],
|
|
});
|
|
|
|
const handleResize = () => chartRef.current?.resize();
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
return () => {
|
|
window.removeEventListener('resize', handleResize);
|
|
chartRef.current?.dispose();
|
|
chartRef.current = null;
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const chart = chartRef.current;
|
|
if (!chart) return;
|
|
|
|
const isActive = activeIndex !== null && activeIndex !== undefined;
|
|
|
|
const target = isActive ? rotationNeeded[activeIndex] : 0;
|
|
const newAngle = shortestRotation(rotationRef.current, target);
|
|
rotationRef.current = newAngle;
|
|
setRotation(newAngle);
|
|
|
|
chart.setOption({
|
|
series: [{
|
|
data: chartData.map((d, i) => ({
|
|
...d,
|
|
label: { color: isActive && activeIndex === i ? '#fff' : safeColors[i] },
|
|
itemStyle: {
|
|
color: isActive && activeIndex !== i ? hexToRgba(safeColors[i], 0.2) : safeColors[i],
|
|
opacity: 1,
|
|
borderRadius: 6,
|
|
borderColor: 'rgba(15,23,42,0.8)',
|
|
borderWidth: 0,
|
|
},
|
|
})),
|
|
}],
|
|
});
|
|
}, [activeIndex]);
|
|
|
|
useEffect(() => {
|
|
const chart = chartRef.current;
|
|
if (!chart) return;
|
|
|
|
const handler = (params) => {
|
|
if (onSelect && params.dataIndex !== undefined) {
|
|
onSelect(params.dataIndex);
|
|
}
|
|
};
|
|
chart.on('click', handler);
|
|
return () => chart.off('click', handler);
|
|
}, [onSelect]);
|
|
|
|
return (
|
|
<div
|
|
className="inline-block w-full"
|
|
style={{
|
|
transform: `rotate(${rotation}deg)`,
|
|
transition: 'transform 1s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
}}
|
|
>
|
|
<div ref={containerRef} className="w-full h-[600px] md:h-[720px]" />
|
|
</div>
|
|
);
|
|
}
|