mirror of https://github.com/buster-so/buster.git
cross hair animations
This commit is contained in:
parent
5d4bde46a0
commit
0f5648c2c8
|
@ -24,6 +24,7 @@ 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';
|
||||||
|
@ -69,7 +70,8 @@ ChartJS.register(
|
||||||
LogarithmicScale,
|
LogarithmicScale,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
TimeSeriesScale,
|
TimeSeriesScale,
|
||||||
ChartDataLabels
|
ChartDataLabels,
|
||||||
|
CrosshairPlugin
|
||||||
);
|
);
|
||||||
|
|
||||||
ChartJS.defaults.responsive = true;
|
ChartJS.defaults.responsive = true;
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
import { Chart, ChartType } from 'chart.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration options for the Animation Delay plugin
|
|
||||||
*/
|
|
||||||
interface AnimationDelayPluginOptions {
|
|
||||||
/** Delay between each data point animation in milliseconds */
|
|
||||||
delayBetweenPoints?: number;
|
|
||||||
/** Additional delay between datasets in milliseconds */
|
|
||||||
delayBetweenDatasets?: number;
|
|
||||||
/** Whether to reset the delay when the chart is updated */
|
|
||||||
resetOnUpdate?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context object passed to the delay function
|
|
||||||
interface DelayFunctionContext {
|
|
||||||
type: string;
|
|
||||||
mode: string;
|
|
||||||
dataIndex: number;
|
|
||||||
datasetIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chart.js plugin for animating chart elements with a sequential delay effect.
|
|
||||||
* This creates a "build-up" animation where each data point appears after the previous one.
|
|
||||||
*/
|
|
||||||
const AnimationDelayPlugin = {
|
|
||||||
id: 'animationDelay',
|
|
||||||
|
|
||||||
defaults: {
|
|
||||||
delayBetweenPoints: 150,
|
|
||||||
delayBetweenDatasets: 50,
|
|
||||||
resetOnUpdate: true
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeInit(chart: Chart): void {
|
|
||||||
// Add flag to track if animation delay is active
|
|
||||||
chart._animationDelayActive = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeUpdate(chart: Chart): void {
|
|
||||||
const options = chart.options.plugins?.animationDelay || {};
|
|
||||||
const pluginOptions = {
|
|
||||||
...this.defaults,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pluginOptions.resetOnUpdate) {
|
|
||||||
chart._animationDelayActive = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDraw(chart: Chart): void {
|
|
||||||
// Skip if animation delay is already applied
|
|
||||||
if (chart._animationDelayActive) return;
|
|
||||||
|
|
||||||
const options = chart.options.plugins?.animationDelay || {};
|
|
||||||
const pluginOptions = {
|
|
||||||
...this.defaults,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure animation object exists
|
|
||||||
if (!chart.options.animation) {
|
|
||||||
chart.options.animation = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store original settings
|
|
||||||
const originalAnimationObj = { ...chart.options.animation };
|
|
||||||
|
|
||||||
// Apply new animation with delay
|
|
||||||
chart.options.animation = {
|
|
||||||
...originalAnimationObj,
|
|
||||||
|
|
||||||
// Define the delay function to create sequential animation effect
|
|
||||||
delay(context: DelayFunctionContext): number {
|
|
||||||
let delay = 0;
|
|
||||||
|
|
||||||
// Only apply to data points in default mode
|
|
||||||
if (context.type === 'data' && context.mode === 'default' && !chart._animationDelayActive) {
|
|
||||||
// Calculate delay based on dataIndex and datasetIndex
|
|
||||||
delay =
|
|
||||||
context.dataIndex * (pluginOptions.delayBetweenPoints || 150) +
|
|
||||||
context.datasetIndex * (pluginOptions.delayBetweenDatasets || 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
return delay;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Override onComplete function
|
|
||||||
onComplete(): void {
|
|
||||||
// Mark animation as active once complete
|
|
||||||
chart._animationDelayActive = true;
|
|
||||||
|
|
||||||
// Call original onComplete if it exists
|
|
||||||
if (originalAnimationObj.onComplete) {
|
|
||||||
originalAnimationObj.onComplete.call(chart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register the plugin globally
|
|
||||||
Chart.register(AnimationDelayPlugin);
|
|
||||||
|
|
||||||
// Add TypeScript type definitions
|
|
||||||
declare module 'chart.js' {
|
|
||||||
interface Chart {
|
|
||||||
_animationDelayActive: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PluginOptionsByType<TType extends ChartType> {
|
|
||||||
animationDelay?: AnimationDelayPluginOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnimationDelayPlugin;
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { Chart, ChartEvent, Plugin } from 'chart.js';
|
||||||
|
|
||||||
|
declare module 'chart.js' {
|
||||||
|
interface Chart {
|
||||||
|
$crosshair?: {
|
||||||
|
x: number | null;
|
||||||
|
y: number | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CrosshairPluginOptions {
|
||||||
|
lineColor?: string;
|
||||||
|
lineWidth?: number;
|
||||||
|
lineDash?: number[];
|
||||||
|
labelBackgroundColor?: string;
|
||||||
|
labelFont?: string;
|
||||||
|
labelFontColor?: string;
|
||||||
|
labelHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crosshairPlugin: Plugin<'line'> = {
|
||||||
|
id: 'crosshairPlugin',
|
||||||
|
|
||||||
|
defaults: {
|
||||||
|
lineColor: 'rgba(102, 102, 102, 1)',
|
||||||
|
lineWidth: 2,
|
||||||
|
lineDash: [6, 6],
|
||||||
|
labelBackgroundColor: 'rgba(102, 102, 102, 1)',
|
||||||
|
labelFont: 'bold 12px sans-serif',
|
||||||
|
labelFontColor: 'white',
|
||||||
|
labelHeight: 24
|
||||||
|
},
|
||||||
|
|
||||||
|
// Initialize the crosshair state
|
||||||
|
beforeInit(chart: Chart) {
|
||||||
|
chart.$crosshair = { x: null, y: null };
|
||||||
|
},
|
||||||
|
|
||||||
|
// Capture mouse events to update the crosshair coordinates
|
||||||
|
afterEvent(chart: Chart, args: { event: ChartEvent }) {
|
||||||
|
const event = args.event;
|
||||||
|
if (event.type === 'mousemove') {
|
||||||
|
chart.$crosshair = chart.$crosshair || { x: null, y: null };
|
||||||
|
chart.$crosshair.x = event.x;
|
||||||
|
chart.$crosshair.y = event.y;
|
||||||
|
} else if (event.type === 'mouseout') {
|
||||||
|
if (chart.$crosshair) {
|
||||||
|
chart.$crosshair.x = null;
|
||||||
|
chart.$crosshair.y = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Draw the crosshair lines and labels after the chart is rendered
|
||||||
|
afterDraw(chart: Chart, args, options: CrosshairPluginOptions) {
|
||||||
|
const {
|
||||||
|
ctx,
|
||||||
|
chartArea: { top, bottom, left, right }
|
||||||
|
} = chart;
|
||||||
|
const crosshair = chart.$crosshair;
|
||||||
|
if (!crosshair) return;
|
||||||
|
const { x, y } = crosshair;
|
||||||
|
|
||||||
|
// Only draw if the pointer is within the chart area
|
||||||
|
if (x !== null && y !== null && x >= left && x <= right && y >= top && y <= bottom) {
|
||||||
|
ctx.save();
|
||||||
|
|
||||||
|
// Set common line styles
|
||||||
|
ctx.strokeStyle = options.lineColor || 'rgba(102, 102, 102, 1)';
|
||||||
|
ctx.lineWidth = options.lineWidth || 2;
|
||||||
|
ctx.setLineDash(options.lineDash || [6, 6]);
|
||||||
|
|
||||||
|
// Draw vertical line
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, top);
|
||||||
|
ctx.lineTo(x, bottom);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
// Draw horizontal line
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(left, y);
|
||||||
|
ctx.lineTo(right, y);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
// Reset dash settings
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
|
||||||
|
const LABEL_HEIGHT = options.labelHeight || 24;
|
||||||
|
|
||||||
|
// Get the scales for label values
|
||||||
|
const yScale = chart.scales.y;
|
||||||
|
const xScale = chart.scales.x;
|
||||||
|
|
||||||
|
// --- Draw Y-Axis Label ---
|
||||||
|
const yValue = yScale.getValueForPixel(y);
|
||||||
|
const yLabel = yValue?.toFixed(2) || '';
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = options.labelBackgroundColor || 'rgba(102, 102, 102, 1)';
|
||||||
|
// Draw label rectangle on left margin
|
||||||
|
ctx.fillRect(0, y - LABEL_HEIGHT / 2, left, LABEL_HEIGHT);
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
ctx.font = options.labelFont || 'bold 12px sans-serif';
|
||||||
|
ctx.fillStyle = options.labelFontColor || 'white';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(yLabel, left / 2, y);
|
||||||
|
|
||||||
|
// --- Draw X-Axis Label ---
|
||||||
|
const xValue = xScale.getValueForPixel(x) || 0;
|
||||||
|
const xDate = new Date(xValue);
|
||||||
|
const xLabel = xDate.toLocaleString('en-US', { day: 'numeric', month: 'long' });
|
||||||
|
const textWidth = ctx.measureText(xLabel).width;
|
||||||
|
const padding = 12;
|
||||||
|
const labelWidth = textWidth + padding;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = options.labelBackgroundColor || 'rgba(102, 102, 102, 1)';
|
||||||
|
ctx.fillRect(x - labelWidth / 2, bottom, labelWidth, LABEL_HEIGHT);
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
ctx.fillStyle = options.labelFontColor || 'white';
|
||||||
|
ctx.fillText(xLabel, x, bottom + LABEL_HEIGHT / 2);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default crosshairPlugin;
|
|
@ -0,0 +1 @@
|
||||||
|
https://andrew-dev-p.github.io/chartjs-showcase/drill-down-bar.html
|
Loading…
Reference in New Issue