UNPKG

react-native-chart-kit

Version:

Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.

88 lines (87 loc) 3.65 kB
import { useEffect, useRef, useState } from "react"; const tooltipPositionThreshold = 0.5; const easeOutCubic = (progress) => { const clampedProgress = Math.min(Math.max(progress, 0), 1); return 1 - Math.pow(1 - clampedProgress, 3); }; const interpolate = (from, to, progress) => from + (to - from) * progress; export const getBarChartTooltipAnimationTargetKey = (tooltip) => `${tooltip.bar.key}:${tooltip.x}:${tooltip.y}`; export const useAnimatedBarChartTooltipModel = (tooltip, positionAnimationDuration) => { const latestPositionRef = useRef(undefined); const previousTooltipKeyRef = useRef(undefined); const [animatedPosition, setAnimatedPosition] = useState(undefined); const targetKey = tooltip ? getBarChartTooltipAnimationTargetKey(tooltip) : undefined; const targetX = tooltip?.x; const targetY = tooltip?.y; useEffect(() => { let animationFrame = 0; if (targetKey === undefined || targetX === undefined || targetY === undefined) { latestPositionRef.current = undefined; previousTooltipKeyRef.current = undefined; animationFrame = requestAnimationFrame(() => { setAnimatedPosition(undefined); }); return () => { cancelAnimationFrame(animationFrame); }; } const targetPosition = { x: targetX, y: targetY }; const currentPosition = latestPositionRef.current ?? targetPosition; const hasPreviousTooltip = previousTooltipKeyRef.current !== undefined; const duration = positionAnimationDuration; previousTooltipKeyRef.current = targetKey; if (!hasPreviousTooltip || duration <= 0) { latestPositionRef.current = targetPosition; animationFrame = requestAnimationFrame(() => { setAnimatedPosition(targetPosition); }); return () => { cancelAnimationFrame(animationFrame); }; } const deltaX = Math.abs(currentPosition.x - targetPosition.x); const deltaY = Math.abs(currentPosition.y - targetPosition.y); if (deltaX < tooltipPositionThreshold && deltaY < tooltipPositionThreshold) { latestPositionRef.current = targetPosition; animationFrame = requestAnimationFrame(() => { setAnimatedPosition(targetPosition); }); return () => { cancelAnimationFrame(animationFrame); }; } let startTime; const tick = (timestamp) => { startTime ?? (startTime = timestamp); const rawProgress = Math.min((timestamp - startTime) / duration, 1); const progress = easeOutCubic(rawProgress); const nextPosition = { x: interpolate(currentPosition.x, targetPosition.x, progress), y: interpolate(currentPosition.y, targetPosition.y, progress) }; latestPositionRef.current = nextPosition; setAnimatedPosition(nextPosition); if (rawProgress < 1) { animationFrame = requestAnimationFrame(tick); } }; animationFrame = requestAnimationFrame(tick); return () => { cancelAnimationFrame(animationFrame); }; }, [positionAnimationDuration, targetKey, targetX, targetY]); if (!tooltip) { return undefined; } const position = animatedPosition ?? { x: tooltip.x, y: tooltip.y }; return { ...tooltip, x: position.x, y: position.y }; };