学以不仅致用,还要融汇贯通
一直以来,很多人都陷入一个误区,工作中用到的技术方案,搜集各方资料,做出来以后,交付就了事。这种态度只会让自己养成非常不好的做事态度。
本次使用svg来制作环形图动画就是一个例子,要深入了解一下其中的属性和原理,这样在下次遇到类似问题,就可以快速上手,避免再次从零开始。
import { useEffect, useState } from "react";
type RingPieItem = {
value: number;
color: string;
label: string;
hideLabel?: boolean;
};
export default (props: {
ringRadius?: number;
strokeWitdh?: number;
startAngle?: number;
patterns: RingPieItem[];
visible: boolean;
}) => {
const { ringRadius = 100, strokeWitdh = 4, startAngle = 0, patterns, visible } = props;
let radius = ringRadius / 2 < strokeWitdh ? ringRadius / 2 - 2 : ringRadius / 2 - strokeWitdh / 2;
let stroke = ringRadius / 2 < strokeWitdh ? 2 : strokeWitdh;
const centerPointer = ringRadius / 2;
const gapPercent = 0.005;
const gapAngle = gapPercent * 360;
const total = patterns.reduce((acc, cur) => acc + cur.value, 0);
const [anim, setAnim] = useState(false);
useEffect(() => { setAnim(visible) }, [visible]);
let angle = startAngle - 90;
let patternsData = patterns.map((item, index) => {
let preItemPercent = total === 0 ? 0 : (patterns[index - 1] || { value: 0 }).value / total;
let percent = item.value / total;
let legendValue = total === 0 ? 0 : Math.floor(item.value / total * 10000) / 100;
angle += preItemPercent * 360;
return {
...item,
angle,
percent,
legendValue,
preItemPercent,
initPercent: 0,
x1: centerPointer + Math.sin((angle - gapAngle / 2) / 180 * Math.PI) * radius,
y1: centerPointer - Math.cos((angle - gapAngle / 2) / 180 * Math.PI) * radius,
x2: centerPointer + Math.sin((angle + gapAngle / 2) / 180 * Math.PI) * radius,
y2: centerPointer - Math.cos((angle + gapAngle / 2) / 180 * Math.PI) * radius,
};
});
let further = false;
patternsData = patternsData.map((item, index) => {
let legendValue = item.legendValue;
if (!further && item.value !== 0) {
further = true;
legendValue = (10000 - patternsData.reduce((i, j, k) => i + (index === k ? 0 : j.legendValue * 100), 0)) / 100;
}
return { ...item, legendValue }
})
return <div style={{ display: 'flex', flexWrap: 'nowrap', justifyContent: 'space-between' }}>
<div style={{ flexBasis: ringRadius }}>
<svg style={{ width: ringRadius, height: ringRadius }} viewBox={`0 0 ${ringRadius} ${ringRadius}`}
version="1.1" xmlns="http://www.w3.org/2000/svg">
{
patternsData.map(({ percent, color, angle, initPercent }, index) => {
return <circle
key={index}
cx={centerPointer}
cy={centerPointer}
r={radius}
strokeWidth={stroke}
stroke={color}
fill='none'
style={{ transition: 'stroke-dasharray 0.2s ease-in-out', transform: `rotate(${angle}deg)`, transformOrigin: 'center' }}
strokeDasharray={`${3.1415 * radius * 2 * (anim ? percent : initPercent)} ${3.1415 * radius * 2}`}
/>
})
}
{
anim && patternsData.map(({ x1, x2, y1, y2 }, index) => {
return <line
{...{ x1, x2, y1, y2 }}
key={index}
stroke="#fff"
strokeWidth={stroke}
style={{ position: 'relative', zIndex: 100, transform: `rotate(${90}deg)`, transformOrigin: 'center' }}
/>
})
}
</svg>
</div>
<div className="legend" style={{ transition: 'all 0.35s ease-in-out', opacity: anim ? 1 : 0, transform: `translateX(${anim ? 0 : -8})`, marginTop: -5, display: 'flex', flexDirection: 'column', paddingBottom: 3, justifyContent: 'space-between', marginLeft: '24px' }}>
{
patternsData.map((item) => {
return !item.hideLabel && <div className="legend-item" key={item.label} style={{ height: 18, whiteSpace: 'nowrap' }}>
<div
className="legend-item-color"
style={{ background: item.color, display: 'inline-block', width: 14, height: 8, borderRadius: 2, marginRight: 8 }}
/>
<div
className="legend-item-text"
style={{ display: 'inline-block', width: 14, textAlign: 'center', color: 'rgba(0, 0, 0, 0.85)', marginRight: 8, fontSize: 12, }}
>
{item.label}
</div>
<div
className="legend-item-value"
style={{ display: 'inline-block', color: 'rgba(0, 0, 0, 0.85)', fontSize: 12, textAlign: 'left' }}
>
{item.legendValue}%
</div>
</div>
})
}
</div>
</div >
}