fixed hover effects (transition still broken)

This commit is contained in:
myung03 2024-05-17 16:45:21 -07:00
parent 91860b8b13
commit 22b8c47403

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, FunctionComponent } from 'react';
import React, { useState, useRef, FunctionComponent } from 'react';
import clsx from 'clsx';
export type PieChartProps = {
@ -11,14 +11,6 @@ export type PieChartProps = {
units?: string;
};
const polarToCartesian = (centerX: number, centerY: number, radius: number, angleInDegrees: number) => {
const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;
return {
x: centerX + radius * Math.cos(angleInRadians),
y: centerY + radius * Math.sin(angleInRadians),
};
};
const PieChart: FunctionComponent<PieChartProps> = ({
data,
radius,
@ -29,26 +21,22 @@ const PieChart: FunctionComponent<PieChartProps> = ({
units = '',
}) => {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const pieChartRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleMouseOut = (event: MouseEvent) => {
if (pieChartRef.current && !pieChartRef.current.contains(event.target as Node)) {
setHoveredIndex(null);
}
};
document.addEventListener('mouseout', handleMouseOut);
return () => {
document.removeEventListener('mouseout', handleMouseOut);
};
}, []);
const total = data.reduce((sum, item) => sum + item.value, 0);
const margin = 10;
const svgSize = 2 * (radius + strokeWidth) + margin * 2;
const center = radius + strokeWidth + margin;
const polarToCartesian = (centerX: number, centerY: number, radius: number, angleInDegrees: number) => {
const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;
return {
x: centerX + radius * Math.cos(angleInRadians),
y: centerY + radius * Math.sin(angleInRadians),
};
};
const renderSlices = () => {
let cumulativeValue = 0;
@ -67,75 +55,75 @@ const PieChart: FunctionComponent<PieChartProps> = ({
const innerEnd = polarToCartesian(center, center, innerRadius!, startAngle);
const largeArcFlag = sliceAngle > 180 ? 1 : 0;
const outerPathData = [
const pathData = [
`M ${outerStart.x} ${outerStart.y}`,
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${outerEnd.x} ${outerEnd.y}`,
`L ${center} ${center}`,
`Z`,
].join(' ');
const innerPathData = [
`M ${innerStart.x} ${innerStart.y}`,
`L ${innerStart.x} ${innerStart.y}`,
`A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${innerEnd.x} ${innerEnd.y}`,
`L ${center} ${center}`,
`Z`,
].join(' ');
const fullPathData = [
`M ${outerStart.x} ${outerStart.y}`,
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${outerEnd.x} ${outerEnd.y}`,
`L ${innerEnd.x} ${innerEnd.y}`,
`A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${innerStart.x} ${innerStart.y}`,
const hoverPathData = [
`M ${polarToCartesian(center, center, radius + 10, startAngle).x} ${polarToCartesian(center, center, radius + 10, startAngle).y}`,
`A ${radius + 10} ${radius + 10} 0 ${largeArcFlag} 1 ${polarToCartesian(center, center, radius + 10, startAngle + sliceAngle).x} ${polarToCartesian(center, center, radius + 10, startAngle + sliceAngle).y}`,
`L ${polarToCartesian(center, center, innerRadius! - 10, startAngle + sliceAngle).x} ${polarToCartesian(center, center, innerRadius! - 10, startAngle + sliceAngle).y}`,
`A ${innerRadius! - 10} ${innerRadius! - 10} 0 ${largeArcFlag} 0 ${polarToCartesian(center, center, innerRadius! - 10, startAngle).x} ${polarToCartesian(center, center, innerRadius! - 10, startAngle).y}`,
`Z`,
].join(' ');
return (
<g
key={index}
onMouseOver={() => setHoveredIndex(index)}
onMouseOut={() => setHoveredIndex(null)}
className={clsx('transition-transform duration-300 ease-out', {
'opacity-100': isHovered,
'opacity-50': hoveredIndex !== null && !isHovered,
})}
style={{
transform: isHovered ? `translate(${translation.x}px, ${translation.y}px) scale(1.05)` : 'scale(1)',
opacity: hoveredIndex !== null && !isHovered ? 0.5 : 1,
transition: 'transform 0.3s ease-out, opacity 0.3s ease-out',
zIndex: isHovered ? 10 : 1,
}}
>
{/* Full path for the whole slice, used for hover effect */}
<path d={fullPathData} fill="transparent" />
{/* Path for the outer portion of the slice, with color */}
<path d={outerPathData} fill={colors[index % colors.length]} />
<g className={clsx('transition-transform duration-300 ease-out', {
'opacity-100': isHovered,
'opacity-50': hoveredIndex !== null && !isHovered,
})}
key={index}>
<path
d={hoverPathData}
fill="transparent"
pointerEvents="bounding-box"
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
style={{ cursor: 'pointer' }}
/>
<path
d={pathData}
fill={colors[index % colors.length]}
stroke="white"
strokeWidth={strokeWidth}
className={clsx('transition-transform duration-300 ease-out', {
'opacity-100': isHovered,
'opacity-50': hoveredIndex !== null && !isHovered,
})}
style={{
transform: isHovered ? `translate(${translation.x}px, ${translation.y}px) scale(1.05)` : 'scale(1)',
opacity: hoveredIndex !== null && !isHovered ? 0.5 : 1,
}}
/>
</g>
);
});
};
return (
<div ref={pieChartRef} className="relative inline-block">
<div ref={pieChartRef} className="relative inline-block transition-all">
<div
className={clsx('relative', className)}
style={{ width: `${svgSize}px`, height: `${svgSize}px` }}
>
<svg width={svgSize} height={svgSize} viewBox={`0 0 ${svgSize} ${svgSize}`}>
{renderSlices()}
{hoveredIndex !== null && (
<text
x={center}
y={center}
textAnchor="middle"
dominantBaseline="central"
className="pointer-events-none text-lg font-bold transition-opacity duration-300 ease-out"
style={{ zIndex: 10 }}
>
{data[hoveredIndex]?.value}
<tspan className="ml-0.5 text-tiny text-ink-faint">{units}</tspan>
</text>
)}
</svg>
{hoveredIndex !== null && (
<div
className="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded p-1.5 text-lg font-bold transition-transform duration-300 ease-out"
style={{ zIndex: 10 }}
>
{data[hoveredIndex].value}
<span className="ml-0.5 text-tiny text-ink-faint">
{units}
</span>
</div>
)}
</div>
</div>
);