UNPKG

react-native-chart-kit

Version:

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

104 lines (103 loc) 4.08 kB
import { useEffect, useMemo, useState } from "react"; import { buildProgressRings } from "../../../core/index"; const defaultProgressAnimationDuration = 1200; const defaultProgressAnimationStagger = 0.08; export const resolveProgressChartAnimationConfig = (animation) => { if (!animation) { return { duration: 0, enabled: false, stagger: 0 }; } const config = typeof animation === "object" ? animation : {}; const duration = typeof config.duration === "number" && Number.isFinite(config.duration) ? Math.max(0, config.duration) : defaultProgressAnimationDuration; const stagger = typeof config.stagger === "number" && Number.isFinite(config.stagger) ? Math.max(0, Math.min(0.5, config.stagger)) : defaultProgressAnimationStagger; return { duration, enabled: true, stagger }; }; const clamp01 = (value) => Math.max(0, Math.min(1, value)); const easeOutCubic = (progress) => { const clampedProgress = clamp01(progress); return 1 - Math.pow(1 - clampedProgress, 3); }; export const getProgressRingAnimationProgress = ({ index, progress, stagger }) => { const delay = Math.min(0.92, index * stagger); return easeOutCubic((progress - delay) / Math.max(0.01, 1 - delay)); }; export const getAnimatedProgressRing = ({ centerX, centerY, progress, ring }) => { if (!ring.defined || progress >= 0.999) { return ring; } const animatedValue = ring.clampedValue * clamp01(progress); const animatedInput = { defined: ring.defined, index: ring.index, value: animatedValue, ...(ring.color !== undefined ? { color: ring.color } : {}), ...(ring.label !== undefined ? { label: ring.label } : {}) }; const animatedRing = buildProgressRings({ centerX, centerY, maxRadius: ring.radius, ringGap: 0, rings: [animatedInput], startAngle: ring.startAngle, strokeWidth: ring.strokeWidth })[0]; if (!animatedRing) { return ring; } return { ...ring, clampedValue: animatedRing.clampedValue, defined: animatedRing.defined, endAngle: animatedRing.endAngle, path: animatedRing.path }; }; export const getAnimatedProgressRings = ({ centerX, centerY, progress, rings, stagger }) => rings.map((ring, index) => getAnimatedProgressRing({ centerX, centerY, progress: getProgressRingAnimationProgress({ index, progress, stagger }), ring })); export const getAverageProgress = (rings) => { const definedRings = rings.filter((ring) => ring.value !== null); return definedRings.length > 0 ? definedRings.reduce((sum, ring) => sum + ring.clampedValue, 0) / definedRings.length : 0; }; export const getProgressChartAnimationTargetKey = (rings) => rings .map((ring) => `${ring.index}:${ring.defined}:${ring.clampedValue}`) .join("|"); export const useProgressChartAnimation = ({ animation, targetKey }) => { const config = useMemo(() => resolveProgressChartAnimationConfig(animation), [animation]); const [progress, setProgress] = useState(config.enabled ? 0 : 1); useEffect(() => { if (!config.enabled || config.duration <= 0) { const frame = requestAnimationFrame(() => setProgress(1)); return () => cancelAnimationFrame(frame); } let animationFrame = 0; let startTime; const tick = (timestamp) => { startTime ?? (startTime = timestamp); const nextProgress = clamp01((timestamp - startTime) / config.duration); setProgress(nextProgress); if (nextProgress < 1) { animationFrame = requestAnimationFrame(tick); } }; animationFrame = requestAnimationFrame((timestamp) => { setProgress(0); tick(timestamp); }); return () => cancelAnimationFrame(animationFrame); }, [config.duration, config.enabled, targetKey]); return { progress, stagger: config.stagger }; };