import React, { memo, useCallback, useEffect, useState } from 'react'; import { LayoutChangeEvent, View } from 'react-native'; import { Canvas, useFont, Path, runTiming, Skia, Text, useComputedValue, useValue, Group, DashPathEffect, } from '@shopify/react-native-skia'; import { Easing } from 'react-native-reanimated'; import { scaleLinear, scalePoint } from 'd3-scale'; import type { BarChartProps, ChartPoint } from './types'; import { CHART_BAR_COLOR, CHART_BAR_WIDTH, CHART_FONT_SIZE, CHART_HEIGHT, CHART_HORIZONTAL_MARGIN, CHART_VERTICAL_MARGIN, CHART_WIDTH, } from './constants'; export const BarChart = memo( ({ // isLoading, fontFile, yAxisMax, fontSize = CHART_FONT_SIZE, labelsColor = 'black', // startDate: startDateProp, // endDate: endDateProp, paddingHorizontal = CHART_HORIZONTAL_MARGIN, paddingVertical = CHART_VERTICAL_MARGIN, barWidth: barWidthProp = CHART_BAR_WIDTH, datasets = [], }: BarChartProps) => { // only the first item of datasets prop will be used, other items will be ignored. const [{ data = [], color: chartColor = CHART_BAR_COLOR } = {}] = datasets; const [canvasWidth, setCanvasWidth] = useState(CHART_WIDTH); const [canvasHeight, setCanvasHeight] = useState(CHART_HEIGHT); const animationState = useValue(0); const font = useFont(fontFile, fontSize); // define chart boundaries // const startDate = startDateProp || getMinMaxDate(data, 'min'); // const endDate = endDateProp || getMinMaxDate(data, 'max'); // const totalCount = data.length; // const xLabelsInterval = getXLabelsInterval(totalCount); const xScaleBounds = [ paddingHorizontal, canvasWidth - paddingHorizontal, ] as const; const chartHeight = canvasHeight - paddingVertical; const yScaleBounds = [paddingVertical, chartHeight] as const; const canvasStyles = { width: canvasWidth, height: canvasHeight, }; const xScale = scalePoint() .domain(data.map((d) => d.x.toString())) .range(xScaleBounds) .align(0); const barWidth = Math.min(barWidthProp, xScale.step()); const yScaleDomain = [0, yAxisMax || Math.max(...data.map((d) => d.y))]; const yScale = scaleLinear().domain(yScaleDomain).range(yScaleBounds); const animate = () => { animationState.current = 0; runTiming(animationState, 1, { duration: 1000, easing: Easing.inOut(Easing.exp), }); }; useEffect(() => { // this useEffect is responsible for toggling chart animationState.current = 0; setTimeout(animate, 0); // eslint-disable-next-line react-hooks/exhaustive-deps }, [data]); const path = useComputedValue(() => { const newPath = Skia.Path.Make(); data.forEach((dataPoint: ChartPoint) => { const rect = Skia.XYWHRect( xScale(dataPoint.x)! - barWidthProp / 2, chartHeight, barWidth, yScale(dataPoint.y * animationState.current) * -1 ); const rrect = Skia.RRectXY(rect, 8, 8); newPath.addRRect(rrect); }); return newPath; }, [animationState, data]); const onLayout = useCallback( ({ nativeEvent: { layout } }: LayoutChangeEvent) => { setCanvasWidth(Math.round(layout.width)); setCanvasHeight(Math.round(layout.height)); }, [setCanvasWidth] ); if (!font) { return ; } return ( {font ? ( {yScale.ticks(6).map((label: number, idx: number) => { const yPoint = chartHeight - yScale(label); // https://stackoverflow.com/questions/51497534/how-to-force-a-specific-amount-of-y-axis-ticks-in-d3-charts return ( ); })} ) : null} {font ? ( {xScale.domain().map((label, idx: number) => ( ))} ) : null} {/*{data.map((dataPoint: ChartPoint, idx: number) => (*/} {/* */} {/* */} {/* */} {/*))}*/} ); } );