All files / components/hooks/lineChart useLineChartGeometry.ts

0% Statements 0/45
0% Branches 0/20
0% Functions 0/12
0% Lines 0/38

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                                                                                                                                                                                                                     
import { useCallback, useMemo } from "react";
import * as d3 from "d3";
import { DataPoint } from "../../../types/data";
import { getPathLengthAtX } from "./lineChartUtils";
 
interface UseLineChartGeometryArgs {
  dataSet: any[];
  xAxisDataType: "number" | "date_annual" | "date_monthly";
  xScale: any;
  yScale: any;
}
 
export function useLineChartGeometry({
  dataSet,
  xAxisDataType,
  xScale,
  yScale,
}: UseLineChartGeometryArgs) {
  // Get Y value at X
  const getYValueAtX = useCallback((series: DataPoint[], x: number | Date): number | undefined => {
    if (x instanceof Date) {
      const dataPoint = series.find(d => new Date(d.date).getTime() === x.getTime());
      return dataPoint ? dataPoint.value : undefined;
    }
    const dataPoint = series.find(d => Number(d.date) === x);
    return dataPoint ? dataPoint.value : undefined;
  }, []);
 
  // D3 line generator
  const line = useCallback(
    ({ d, curve }: { d: Iterable<DataPoint>; curve: string }) => {
      return d3
        .line<DataPoint>()
        .x(d => {
          if (xAxisDataType === "number") {
            return xScale(Number(d.date));
          } else if (xAxisDataType === "date_annual") {
            return xScale(new Date(`${d.date}-01-01`));
          } else {
            return xScale(new Date(d.date));
          }
        })
        .y(d => yScale(d.value))
        .curve(d3?.[curve] ?? d3.curveBumpX)(d);
    },
    [xScale, yScale, xAxisDataType]
  );
 
  // Memoized dash array generator
  const getDashArrayMemoized = useMemo(() => {
    return (series: DataPoint[], pathNode: SVGPathElement, xScale: any) => {
      const totalLength = pathNode.getTotalLength();
      const lengths = series.map(d => getPathLengthAtX(pathNode, xScale(new Date(d.date))));
 
      const DASH_LENGTH = 4;
      const DASH_SEPARATOR_LENGTH = 4;
      const dashArray = [];
 
      for (let i = 1; i <= series.length; i++) {
        const segmentLength =
          i === series.length - 1 ? totalLength - lengths[i - 1] : lengths[i] - lengths[i - 1];
 
        if (!series[i]?.certainty) {
          const dashes = Math.floor(segmentLength / (DASH_LENGTH + DASH_SEPARATOR_LENGTH));
          const remainder = Math.ceil(
            segmentLength - dashes * (DASH_LENGTH + DASH_SEPARATOR_LENGTH)
          );
 
          for (let j = 0; j < dashes; j++) {
            dashArray.push(DASH_LENGTH);
            dashArray.push(DASH_SEPARATOR_LENGTH);
          }
 
          if (remainder > 0) dashArray.push(remainder);
        } else {
          if (dashArray.length % 2 === 1) {
            dashArray.push(0);
            dashArray.push(segmentLength);
          } else {
            dashArray.push(segmentLength);
          }
        }
      }
      return dashArray.join(",");
    };
  }, []);
 
  // Memoized line data
  const lineData = useMemo(
    () =>
      dataSet.map(set => ({
        label: set.label,
        color: set.color,
        points: set.series,
      })),
    [dataSet]
  );
 
  return {
    getYValueAtX,
    getPathLengthAtX,
    getDashArrayMemoized,
    line,
    lineData,
  };
}