Chart.js plugin to display labels on data elements
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Interactive label functionality with event listeners for click, hover, enter, and leave events enabling dynamic label behavior and user interactions.
Attach interactive behaviors to data labels with context-aware event handling.
interface EventOptions {
/** Event listener configuration */
listeners?: {
/** Called when a label is clicked */
click?: Listener;
/** Called when mouse enters a label */
enter?: Listener;
/** Called when mouse leaves a label */
leave?: Listener;
};
}
type Listener = (context: Context, event: ChartEvent) => boolean | void;
interface Context {
active: boolean; // Whether element is currently hovered
chart: Chart; // Chart instance
dataIndex: number; // Index of the data point
dataset: ChartDataset; // Dataset containing the data
datasetIndex: number; // Index of the dataset
}Return Values:
true: Update the label and re-render the chartfalse or undefined: No action neededUsage Examples:
// Basic click handler
datalabels: {
listeners: {
click: function(context, event) {
console.log('Clicked:', context.dataset.data[context.dataIndex]);
return false; // No chart update needed
}
}
}
// Hover effects
datalabels: {
backgroundColor: 'lightblue',
listeners: {
enter: function(context, event) {
// Change to hover color
context.backgroundColor = 'darkblue';
return true; // Trigger chart update
},
leave: function(context, event) {
// Reset to default color
context.backgroundColor = 'lightblue';
return true; // Trigger chart update
}
}
}Handle user clicks on data labels for interactive functionality.
Usage Examples:
// Data point selection
datalabels: {
listeners: {
click: function(context, event) {
const chart = context.chart;
const dataIndex = context.dataIndex;
const datasetIndex = context.datasetIndex;
// Toggle data point visibility
const meta = chart.getDatasetMeta(datasetIndex);
meta.data[dataIndex].hidden = !meta.data[dataIndex].hidden;
chart.update();
return false;
}
}
}
// External callbacks
datalabels: {
listeners: {
click: function(context, event) {
// Call external function with data
window.handleDataPointClick({
value: context.dataset.data[context.dataIndex],
label: context.dataset.label,
dataIndex: context.dataIndex,
datasetIndex: context.datasetIndex
});
return false;
}
}
}
// Drill-down functionality
datalabels: {
listeners: {
click: function(context, event) {
const value = context.dataset.data[context.dataIndex];
const label = context.chart.data.labels[context.dataIndex];
// Navigate to detail view
window.location.href = `/details/${label}/${value}`;
return false;
}
}
}Create interactive hover effects that respond to mouse movements.
Usage Examples:
// Highlight on hover
datalabels: {
color: 'black',
font: { weight: 'normal' },
listeners: {
enter: function(context, event) {
// Highlight style
context.color = 'red';
context.font.weight = 'bold';
return true;
},
leave: function(context, event) {
// Reset style
context.color = 'black';
context.font.weight = 'normal';
return true;
}
}
}
// Dynamic content on hover
datalabels: {
formatter: function(value, context) {
if (context.active) {
// Show detailed info when hovered
const percentage = ((value / context.dataset.data.reduce((a, b) => a + b)) * 100).toFixed(1);
return `${value} (${percentage}%)`;
}
return value;
},
listeners: {
enter: function(context, event) {
return true; // Trigger re-render to show detailed format
},
leave: function(context, event) {
return true; // Trigger re-render to show simple format
}
}
}
// Tooltip-like behavior
datalabels: {
display: false, // Hidden by default
listeners: {
enter: function(context, event) {
context.display = true;
return true;
},
leave: function(context, event) {
context.display = false;
return true;
}
}
}Complex interactive behaviors using event combinations.
Multi-State Interactions:
// Track selection state
let selectedPoints = new Set();
datalabels: {
backgroundColor: function(context) {
const key = `${context.datasetIndex}-${context.dataIndex}`;
return selectedPoints.has(key) ? 'orange' : 'lightblue';
},
listeners: {
click: function(context, event) {
const key = `${context.datasetIndex}-${context.dataIndex}`;
if (selectedPoints.has(key)) {
selectedPoints.delete(key);
} else {
selectedPoints.add(key);
}
return true; // Update to reflect selection state
},
enter: function(context, event) {
const key = `${context.datasetIndex}-${context.dataIndex}`;
if (!selectedPoints.has(key)) {
context.backgroundColor = 'yellow'; // Hover state
return true;
}
return false;
},
leave: function(context, event) {
const key = `${context.datasetIndex}-${context.dataIndex}`;
if (!selectedPoints.has(key)) {
context.backgroundColor = 'lightblue'; // Reset
return true;
}
return false;
}
}
}Conditional Event Handling:
// Different behaviors based on data
datalabels: {
listeners: {
click: function(context, event) {
const value = context.dataset.data[context.dataIndex];
if (value > 100) {
// High value action
showDetailModal(context);
} else if (value > 50) {
// Medium value action
highlightRelatedData(context);
} else {
// Low value action
showWarning('Value is quite low');
}
return false;
}
}
}Event Delegation:
// Centralized event handling
function createEventHandler(action) {
return function(context, event) {
// Log all interactions
console.log(`${action} on`, {
dataset: context.dataset.label,
value: context.dataset.data[context.dataIndex],
index: context.dataIndex
});
// Dispatch to appropriate handler
switch (action) {
case 'click':
handleClick(context, event);
break;
case 'enter':
handleHover(context, event, true);
break;
case 'leave':
handleHover(context, event, false);
break;
}
return true;
};
}
datalabels: {
listeners: {
click: createEventHandler('click'),
enter: createEventHandler('enter'),
leave: createEventHandler('leave')
}
}Cross-Chart Interactions:
// Sync multiple charts
datalabels: {
listeners: {
enter: function(context, event) {
// Highlight corresponding data in other charts
const otherCharts = getAllCharts().filter(c => c !== context.chart);
otherCharts.forEach(chart => {
highlightDataPoint(chart, context.dataIndex);
});
return false;
},
leave: function(context, event) {
// Clear highlights in other charts
const otherCharts = getAllCharts().filter(c => c !== context.chart);
otherCharts.forEach(chart => {
clearHighlights(chart);
});
return false;
}
}
}Optimize event handling for smooth interactions.
Debounced Events:
// Debounce hover events for performance
let hoverTimeout;
datalabels: {
listeners: {
enter: function(context, event) {
clearTimeout(hoverTimeout);
hoverTimeout = setTimeout(() => {
// Delayed hover action
performExpensiveHoverAction(context);
}, 150);
return false;
},
leave: function(context, event) {
clearTimeout(hoverTimeout);
return false;
}
}
}Event Throttling:
// Throttle frequent events
let lastEventTime = 0;
const EVENT_THROTTLE = 100; // ms
datalabels: {
listeners: {
enter: function(context, event) {
const now = Date.now();
if (now - lastEventTime < EVENT_THROTTLE) {
return false;
}
lastEventTime = now;
// Handle event
return handleHoverEvent(context, event);
}
}
}Install with Tessl CLI
npx tessl i tessl/npm-chartjs-plugin-datalabels