animation handler

This commit is contained in:
Nate Kelley 2025-04-02 16:20:48 -06:00
parent 0f5648c2c8
commit 26db7bb1df
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 80 additions and 15 deletions

View File

@ -24,7 +24,6 @@ import {
scales scales
} from 'chart.js'; } from 'chart.js';
import { ChartMountedPlugin } from './core/plugins'; import { ChartMountedPlugin } from './core/plugins';
import CrosshairPlugin from './core/plugins/chartjs-plugin-crosshair';
import ChartDeferred from 'chartjs-plugin-deferred'; import ChartDeferred from 'chartjs-plugin-deferred';
import ChartJsAnnotationPlugin from 'chartjs-plugin-annotation'; import ChartJsAnnotationPlugin from 'chartjs-plugin-annotation';
import ChartDataLabels from 'chartjs-plugin-datalabels'; import ChartDataLabels from 'chartjs-plugin-datalabels';
@ -70,8 +69,7 @@ ChartJS.register(
LogarithmicScale, LogarithmicScale,
TimeScale, TimeScale,
TimeSeriesScale, TimeSeriesScale,
ChartDataLabels, ChartDataLabels
CrosshairPlugin
); );
ChartJS.defaults.responsive = true; ChartJS.defaults.responsive = true;

View File

@ -0,0 +1,23 @@
import { AnimationOptions, AnimationSpec } from 'chart.js';
export const barDelayAnimation = (props?: { dataDelay?: number; datasetDelay?: number }) => {
const { dataDelay = 200, datasetDelay = 100 } = props || {};
let delayed = false;
return {
onComplete: () => {
delayed = true;
},
delay: (context) => {
let delay = 0;
const numberOfDatasets = context.chart.data.datasets.length;
const numberOfDataPoints = context.chart.data.datasets[context.datasetIndex].data.length;
if (context.type === 'data' && context.mode === 'default' && !delayed) {
delay = context.dataIndex * dataDelay + context.datasetIndex * datasetDelay;
// Ensure the maximum delay is 1000ms
delay = Math.min(delay, 1000);
}
return delay;
}
} satisfies AnimationOptions<'bar'>['animation'];
};

View File

@ -1,10 +1,21 @@
import { ArcElement, Chart, ChartDataset, ChartMeta, ChartType, Plugin } from 'chart.js'; import {
AnimationSpec,
ArcElement,
Chart,
ChartDataset,
ChartMeta,
ChartType,
Plugin
} from 'chart.js';
import OutLabel from './OutLabel'; import OutLabel from './OutLabel';
import OutLabelsContext from './OutLabelsContext'; import OutLabelsContext from './OutLabelsContext';
import OutLabelsManager from './OutLabelsManager'; import OutLabelsManager from './OutLabelsManager';
import { OutLabelStyle } from './OutLabelsStyle'; import { OutLabelStyle } from './OutLabelsStyle';
import { OutLabelsOptions } from './OutLabelsOptions'; import { OutLabelsOptions } from './OutLabelsOptions';
import { CustomAnimationSpec } from '../common';
interface CustomAnimationSpec extends AnimationSpec<'doughnut' | 'pie'> {
onProgress?: (animation: { currentStep: number; numSteps: number }) => void;
}
const globalAnimationDuration = (Chart.defaults.animation as CustomAnimationSpec).duration; const globalAnimationDuration = (Chart.defaults.animation as CustomAnimationSpec).duration;

View File

@ -1,5 +0,0 @@
import { AnimationSpec } from 'chart.js';
export interface CustomAnimationSpec extends AnimationSpec<'doughnut' | 'pie'> {
onProgress?: (animation: { currentStep: number; numSteps: number }) => void;
}

View File

@ -0,0 +1,38 @@
import { useMemo } from 'react';
import { barDelayAnimation } from '../../core/animations/barDelayAnimation';
import { ANIMATION_DURATION, ANIMATION_THRESHOLD } from '../../../config';
import { AnimationOptions, ChartType as ChartTypeJS } from 'chart.js';
import { ChartType } from '@/api/asset_interfaces/metric';
export const useAnimations = ({
animate,
numberOfSources,
chartType
}: {
animate: boolean;
numberOfSources: number;
chartType: ChartType;
}): AnimationOptions<ChartTypeJS>['animation'] => {
const isAnimationEnabled = useMemo(() => {
return animate && numberOfSources <= ANIMATION_THRESHOLD;
}, [animate, numberOfSources]);
return useMemo(() => {
return isAnimationEnabled
? {
duration: ANIMATION_DURATION,
...animationRecord[chartType]?.()
}
: false;
}, [isAnimationEnabled, chartType]);
};
const animationRecord: Record<ChartType, () => AnimationOptions<ChartTypeJS>['animation']> = {
bar: barDelayAnimation,
line: () => ({}),
scatter: () => ({}),
pie: () => ({}),
metric: () => ({}),
table: () => ({}),
combo: () => ({})
};

View File

@ -23,6 +23,8 @@ import {
LINE_DECIMATION_THRESHOLD, LINE_DECIMATION_THRESHOLD,
TOOLTIP_THRESHOLD TOOLTIP_THRESHOLD
} from '../../../config'; } from '../../../config';
import { barDelayAnimation } from '../../core/animations/barDelayAnimation';
import { useAnimations } from './useAnimations';
interface UseOptionsProps { interface UseOptionsProps {
colors: string[]; colors: string[];
@ -173,9 +175,7 @@ export const useOptions = ({
}, 0); }, 0);
}, [datasetOptions]); }, [datasetOptions]);
const isAnimationEnabled = useMemo(() => { const animation = useAnimations({ animate, numberOfSources, chartType: selectedChartType });
return animate && numberOfSources <= ANIMATION_THRESHOLD;
}, [animate, numberOfSources]);
const disableTooltip = useMemo(() => { const disableTooltip = useMemo(() => {
return disableTooltipProp || numberOfSources >= TOOLTIP_THRESHOLD; return disableTooltipProp || numberOfSources >= TOOLTIP_THRESHOLD;
@ -202,7 +202,7 @@ export const useOptions = ({
return { return {
indexAxis: isHorizontalBar ? 'y' : 'x', indexAxis: isHorizontalBar ? 'y' : 'x',
animation: isAnimationEnabled ? { duration: ANIMATION_DURATION } : false, animation,
backgroundColor: colors, backgroundColor: colors,
borderColor: colors, borderColor: colors,
scales, scales,
@ -236,7 +236,7 @@ export const useOptions = ({
goalLinesAnnotations, goalLinesAnnotations,
trendlineAnnotations, trendlineAnnotations,
tooltipOptions, tooltipOptions,
isAnimationEnabled animate
]); ]);
return options; return options;