UNPKG

react-native-chart-kit

Version:

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

122 lines (121 loc) 4.67 kB
import { useEffect, useRef, useState } from "react"; import { buildPieArcs } from "../../../core/index"; const defaultSelectionAnimationDuration = 180; export const resolvePieChartSelectionAnimationConfig = (animation) => { if (animation === false) { return { enabled: false, duration: 0 }; } const config = typeof animation === "object" ? animation : {}; const duration = typeof config.duration === "number" && Number.isFinite(config.duration) ? Math.max(0, config.duration) : defaultSelectionAnimationDuration; return { enabled: true, duration }; }; 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; const getSliceSelectedAmount = ({ index, selectedIndex }) => (selectedIndex === index ? 1 : 0); const getSliceOpacity = ({ activeOpacity, inactiveOpacity, index, selectedIndex }) => { if (selectedIndex === undefined) { return 1; } return selectedIndex === index ? activeOpacity : inactiveOpacity; }; export const getAnimatedPieSliceOpacity = ({ activeOpacity, inactiveOpacity, index, state }) => interpolate(getSliceOpacity({ activeOpacity, inactiveOpacity, index, selectedIndex: state.fromIndex }), getSliceOpacity({ activeOpacity, inactiveOpacity, index, selectedIndex: state.toIndex }), state.progress); export const getAnimatedPieSlicePath = ({ activeOffset, activeScale, arc, centerX, centerY, innerRadius, radius, state }) => { const selectedAmount = interpolate(getSliceSelectedAmount({ index: arc.index, selectedIndex: state.fromIndex }), getSliceSelectedAmount({ index: arc.index, selectedIndex: state.toIndex }), state.progress); if (selectedAmount <= 0.001) { return arc.path; } const vectorX = arc.centroid.x - centerX; const vectorY = arc.centroid.y - centerY; const length = Math.hypot(vectorX, vectorY); if (length <= 0) { return arc.path; } const offset = activeOffset * selectedAmount; const scale = interpolate(1, activeScale, selectedAmount); const dx = (vectorX / length) * offset; const dy = (vectorY / length) * offset; const shiftedArc = buildPieArcs({ centerX: centerX + dx, centerY: centerY + dy, endAngle: arc.endAngle, innerRadius: innerRadius * scale, radius: radius * scale, slices: [ { defined: true, index: arc.index, label: arc.label, raw: arc.raw, value: 1 } ], startAngle: arc.startAngle })[0]; return shiftedArc?.path ?? arc.path; }; export const usePieChartSelectionAnimation = ({ animation, selectedIndex }) => { const config = resolvePieChartSelectionAnimationConfig(animation); const previousTargetRef = useRef(selectedIndex); const [state, setState] = useState({ fromIndex: selectedIndex, toIndex: selectedIndex, progress: 1 }); useEffect(() => { const fromIndex = previousTargetRef.current; if (fromIndex === selectedIndex) { return undefined; } previousTargetRef.current = selectedIndex; if (!config.enabled || config.duration <= 0) { const animationFrame = requestAnimationFrame(() => { setState({ fromIndex: selectedIndex, toIndex: selectedIndex, progress: 1 }); }); return () => cancelAnimationFrame(animationFrame); } let animationFrame = 0; let startTime; const tick = (timestamp) => { startTime ?? (startTime = timestamp); const rawProgress = Math.min((timestamp - startTime) / config.duration, 1); const progress = easeOutCubic(rawProgress); setState(rawProgress >= 1 ? { fromIndex: selectedIndex, toIndex: selectedIndex, progress: 1 } : { fromIndex, toIndex: selectedIndex, progress }); if (rawProgress < 1) { animationFrame = requestAnimationFrame(tick); } }; animationFrame = requestAnimationFrame((timestamp) => { setState({ fromIndex, toIndex: selectedIndex, progress: 0 }); tick(timestamp); }); return () => cancelAnimationFrame(animationFrame); }, [config.duration, config.enabled, selectedIndex]); return state; };