All files / components/hooks/gapChart useGapChartAnimation.ts

0% Statements 0/44
0% Branches 0/20
0% Functions 0/13
0% Lines 0/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120                                                                                                                                                                                                                                               
import { useEffect, useRef, useState } from "react";
 
interface DataItem {
  label: string;
  code?: string;
  value1: number;
  value2: number;
  difference: number;
  date: string;
}
 
interface AnimationState {
  entering: Set<string>;
  exiting: Set<string>;
  updating: Set<string>;
}
 
export const useGapChartAnimation = (
  processedDataSet: DataItem[],
  enableTransitions: boolean = true
) => {
  const previousDataRef = useRef<DataItem[]>([]);
  const [animationState, setAnimationState] = useState<AnimationState>({
    entering: new Set(),
    exiting: new Set(),
    updating: new Set(),
  });
 
  useEffect(() => {
    if (!enableTransitions) {
      previousDataRef.current = processedDataSet;
      return;
    }
 
    const previousData = previousDataRef.current;
    const currentLabels = new Set(processedDataSet.map(d => d.label));
    const previousLabels = new Set(previousData.map(d => d.label));
 
    // Identify entering items (new items not in previous data)
    const entering = new Set<string>();
    processedDataSet.forEach(d => {
      if (!previousLabels.has(d.label)) {
        entering.add(d.label);
      }
    });
 
    // Identify exiting items (items in previous data but not current)
    const exiting = new Set<string>();
    previousData.forEach(d => {
      if (!currentLabels.has(d.label)) {
        exiting.add(d.label);
      }
    });
 
    // Identify updating items (items that exist in both but may have changed position/values)
    const updating = new Set<string>();
    processedDataSet.forEach((d, index) => {
      const prevIndex = previousData.findIndex(pd => pd.label === d.label);
      if (
        prevIndex !== -1 &&
        (prevIndex !== index ||
          d.value1 !== previousData[prevIndex].value1 ||
          d.value2 !== previousData[prevIndex].value2)
      ) {
        updating.add(d.label);
      }
    });
 
    setAnimationState({ entering, exiting, updating });
 
    // Trigger enter animations
    if (entering.size > 0) {
      // Force reflow to ensure initial state is applied
      requestAnimationFrame(() => {
        setAnimationState(prev => ({
          ...prev,
          entering: new Set(),
        }));
      });
    }
 
    // Update previousDataRef immediately for exiting items
    previousDataRef.current = processedDataSet;
  }, [processedDataSet, enableTransitions]);
 
  // Get opacity for an item based on animation state
  const getItemOpacity = (label: string, defaultOpacity: number = 1) => {
    if (animationState.entering.has(label)) {
      return 0;
    }
    if (animationState.exiting.has(label)) {
      return 0;
    }
    return defaultOpacity;
  };
 
  // Get transform for an item based on animation state
  const getItemTransform = () => {
    // No transform animations, only fade
    return "";
  };
 
  // Don't include exiting items - they should disappear immediately
  const renderDataSet = [...processedDataSet];
 
  // Helper to check if an item should have transitions
  const shouldTransition = (label: string) => {
    // Only apply transitions to updating items, not entering or exiting
    return animationState.updating.has(label);
  };
 
  return {
    animationState,
    getItemOpacity,
    getItemTransform,
    renderDataSet,
    shouldTransition,
  };
};