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
|
||||
} from 'chart.js';
|
||||
import { ChartMountedPlugin } from './core/plugins';
|
||||
import CrosshairPlugin from './core/plugins/chartjs-plugin-crosshair';
|
||||
import ChartDeferred from 'chartjs-plugin-deferred';
|
||||
import ChartJsAnnotationPlugin from 'chartjs-plugin-annotation';
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
|
@ -69,7 +70,8 @@ ChartJS.register(
|
|||
LogarithmicScale,
|
||||
TimeScale,
|
||||
TimeSeriesScale,
|
||||
ChartDataLabels
|
||||
ChartDataLabels,
|
||||
CrosshairPlugin
|
||||
);
|
||||
|
||||
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