react-native-chart-kit
Version:
Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.
88 lines (87 loc) • 3.65 kB
JavaScript
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
};
};