Chart.js
Responsive chart library using Canvas elements. Provides 8 basic chart types, animations, and interaction features. Simple API design enables rapid data visualization.
GitHub Overview
chartjs/Chart.js
Simple HTML5 Charts using the <canvas> tag
Topics
Star History
Framework
Chart.js
Overview
Chart.js is an open-source JavaScript library that makes it easy to create responsive and beautiful charts using HTML5 Canvas.
Details
Chart.js is a dedicated data visualization JavaScript library developed by Nick Downie in 2013. Built on HTML5 Canvas for high-performance rendering, it provides 8 basic chart types (line, bar, radar, doughnut, polar area, bubble, scatter, and mixed charts). Key features include responsive design support, smooth animations, extensive customization options, plugin system extensibility, and full TypeScript support. With its simple configuration and low learning curve, developers can implement complex data visualizations quickly, making it widely adopted in the web development community. It's extensively used in corporate dashboards, analytics tools, reporting features, and admin panels, particularly powerful when combined with modern frameworks like React and Vue.js.
Pros and Cons
Pros
- Simple API: Intuitive and easy-to-learn configuration methods
- Responsive Design: Automatically adapts to device sizes
- Rich Chart Types: 8 basic chart types with numerous variations
- Smooth Animations: Beautiful drawing animations and transitions
- High Customizability: Detailed settings for colors, fonts, labels, etc.
- Lightweight: Relatively small file size with high functionality
- Plugin Ecosystem: Rich third-party plugin ecosystem
- Framework Support: Integration libraries for React, Vue.js, Angular, etc.
Cons
- Canvas Limitations: Lower accessibility compared to DOM-based libraries
- Large Data Performance: Performance degradation with tens of thousands of data points
- Complex Visualization Limits: Difficult to implement non-standard visualizations
- Mobile Interaction: Limited touch events and pinch zoom implementation
- Memory Usage: Memory consumption when displaying multiple charts simultaneously
- No SVG Support: Difficult to output in vector format
Key Links
- Chart.js Official Website
- Chart.js GitHub Repository
- Chart.js Documentation
- Chart.js Samples
- Chart.js Community Plugins
- Chart.js Slack Community
Code Examples
Hello World
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div style="width: 400px; height: 200px;">
<canvas id="myChart"></canvas>
</div>
<script>
// Basic Chart.js usage
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Apple', 'Banana', 'Cherry', 'Date'],
datasets: [{
label: 'Sales Count',
data: [12, 19, 3, 5],
backgroundColor: [
'rgba(255, 99, 132, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(255, 205, 86, 0.8)',
'rgba(75, 192, 192, 0.8)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 205, 86, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Fruit Sales Chart'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
</body>
</html>
Line Chart with Animation
// Line chart creation and animation settings
class AnimatedLineChart {
constructor(canvasId) {
this.ctx = document.getElementById(canvasId).getContext('2d');
this.chart = null;
this.createChart();
}
createChart() {
const data = {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Sales (10k)',
data: [65, 59, 80, 81, 56, 55],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 6,
pointHoverRadius: 8,
pointBorderWidth: 2,
pointBorderColor: '#fff'
}, {
label: 'Profit (10k)',
data: [28, 48, 40, 19, 86, 27],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 6,
pointHoverRadius: 8,
pointBorderWidth: 2,
pointBorderColor: '#fff'
}]
};
const config = {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
title: {
display: true,
text: 'Monthly Sales & Profit Trends',
font: {
size: 16,
weight: 'bold'
},
padding: 20
},
legend: {
display: true,
position: 'top',
labels: {
usePointStyle: true,
padding: 15
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#fff',
bodyColor: '#fff',
borderColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 1,
cornerRadius: 5,
displayColors: true,
callbacks: {
label: function(context) {
return `${context.dataset.label}: $${context.parsed.y}0k`;
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Month'
},
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
},
y: {
display: true,
title: {
display: true,
text: 'Amount ($10k)'
},
grid: {
color: 'rgba(0, 0, 0, 0.1)'
},
beginAtZero: true
}
},
animation: {
duration: 2000,
easing: 'easeInOutQuart',
onComplete: function() {
console.log('Animation completed');
}
},
hover: {
animationDuration: 300
}
}
};
this.chart = new Chart(this.ctx, config);
}
updateData(newData) {
this.chart.data.datasets[0].data = newData.sales;
this.chart.data.datasets[1].data = newData.profit;
this.chart.update('active');
}
addDataPoint(label, salesValue, profitValue) {
this.chart.data.labels.push(label);
this.chart.data.datasets[0].data.push(salesValue);
this.chart.data.datasets[1].data.push(profitValue);
this.chart.update();
}
removeFirstDataPoint() {
this.chart.data.labels.shift();
this.chart.data.datasets[0].data.shift();
this.chart.data.datasets[1].data.shift();
this.chart.update();
}
destroy() {
if (this.chart) {
this.chart.destroy();
}
}
}
// Usage example
const lineChart = new AnimatedLineChart('lineChart');
// Data update example
setTimeout(() => {
lineChart.updateData({
sales: [70, 65, 85, 75, 60, 65],
profit: [35, 45, 42, 25, 80, 30]
});
}, 3000);
Interactive Donut Chart
class InteractiveDonutChart {
constructor(canvasId, data) {
this.ctx = document.getElementById(canvasId).getContext('2d');
this.data = data;
this.chart = null;
this.createChart();
}
createChart() {
const config = {
type: 'doughnut',
data: {
labels: this.data.labels,
datasets: [{
data: this.data.values,
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40',
'#FF6384',
'#C9CBCF'
],
borderColor: '#fff',
borderWidth: 3,
hoverOffset: 15,
hoverBorderWidth: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '60%',
plugins: {
title: {
display: true,
text: 'Product Sales Composition',
font: {
size: 18,
weight: 'bold'
},
padding: {
top: 10,
bottom: 30
}
},
legend: {
display: true,
position: 'right',
labels: {
usePointStyle: true,
padding: 15,
font: {
size: 12
},
generateLabels: function(chart) {
const data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
const value = data.datasets[0].data[i];
const total = data.datasets[0].data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return {
text: `${label}: ${percentage}%`,
fillStyle: data.datasets[0].backgroundColor[i],
strokeStyle: data.datasets[0].borderColor,
lineWidth: data.datasets[0].borderWidth,
index: i
};
});
}
return [];
}
},
onClick: this.legendClickHandler.bind(this)
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#fff',
bodyColor: '#fff',
borderColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 1,
cornerRadius: 8,
displayColors: true,
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `${label}: ${value} units (${percentage}%)`;
},
afterLabel: function(context) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
return `Total: ${total} units`;
}
}
}
},
animation: {
animateRotate: true,
animateScale: true,
duration: 1500,
easing: 'easeOutQuart'
},
hover: {
animationDuration: 300
},
onHover: this.hoverHandler.bind(this),
onClick: this.clickHandler.bind(this)
},
plugins: [{
id: 'centerText',
beforeDraw: this.drawCenterText.bind(this)
}]
};
this.chart = new Chart(this.ctx, config);
}
drawCenterText(chart) {
const { ctx, chartArea: { left, top, width, height } } = chart;
ctx.save();
// Draw center text
const total = chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#333';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const centerX = left + width / 2;
const centerY = top + height / 2;
ctx.fillText(total.toString(), centerX, centerY - 10);
ctx.font = '14px Arial';
ctx.fillStyle = '#666';
ctx.fillText('Total Units', centerX, centerY + 15);
ctx.restore();
}
hoverHandler(event, activeElements) {
if (activeElements.length > 0) {
const element = activeElements[0];
const dataIndex = element.index;
const value = this.chart.data.datasets[0].data[dataIndex];
const label = this.chart.data.labels[dataIndex];
// Custom hover processing
console.log(`Hover: ${label} - ${value} units`);
}
}
clickHandler(event, activeElements) {
if (activeElements.length > 0) {
const element = activeElements[0];
const dataIndex = element.index;
const value = this.chart.data.datasets[0].data[dataIndex];
const label = this.chart.data.labels[dataIndex];
// Show details on click
this.showDetailModal(label, value);
}
}
legendClickHandler(event, legendItem, legend) {
const index = legendItem.index;
const chart = legend.chart;
// Toggle segment visibility
const meta = chart.getDatasetMeta(0);
meta.data[index].hidden = !meta.data[index].hidden;
chart.update();
}
showDetailModal(label, value) {
// Modal display (implementation example)
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 1000;
`;
modal.innerHTML = `
<h3>${label} Details</h3>
<p>Sales Units: ${value}</p>
<p>Share: ${((value / this.chart.data.datasets[0].data.reduce((a, b) => a + b, 0)) * 100).toFixed(1)}%</p>
<button onclick="this.parentElement.remove()">Close</button>
`;
document.body.appendChild(modal);
// Background overlay
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 999;
`;
overlay.onclick = () => {
modal.remove();
overlay.remove();
};
document.body.appendChild(overlay);
}
updateData(newLabels, newValues) {
this.chart.data.labels = newLabels;
this.chart.data.datasets[0].data = newValues;
this.chart.update();
}
exportAsImage() {
const url = this.chart.toBase64Image();
const link = document.createElement('a');
link.download = 'donut-chart.png';
link.href = url;
link.click();
}
destroy() {
if (this.chart) {
this.chart.destroy();
}
}
}
// Usage example
const donutData = {
labels: ['Product A', 'Product B', 'Product C', 'Product D', 'Product E'],
values: [30, 25, 20, 15, 10]
};
const donutChart = new InteractiveDonutChart('donutChart', donutData);
// Data update example
document.getElementById('updateButton').addEventListener('click', () => {
donutChart.updateData(
['New Product A', 'New Product B', 'New Product C'],
[40, 35, 25]
);
});
// Image export example
document.getElementById('exportButton').addEventListener('click', () => {
donutChart.exportAsImage();
});
Mixed Chart (Bar + Line)
class MixedChart {
constructor(canvasId) {
this.ctx = document.getElementById(canvasId).getContext('2d');
this.chart = null;
this.createChart();
}
createChart() {
const data = {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
type: 'bar',
label: 'Revenue ($M)',
data: [120, 150, 180, 200],
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 2,
yAxisID: 'y'
}, {
type: 'bar',
label: 'Cost ($M)',
data: [80, 90, 110, 120],
backgroundColor: 'rgba(255, 99, 132, 0.6)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
yAxisID: 'y'
}, {
type: 'line',
label: 'Profit Margin (%)',
data: [33.3, 40.0, 38.9, 40.0],
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
borderWidth: 3,
fill: false,
tension: 0.3,
pointRadius: 6,
pointHoverRadius: 8,
pointBorderWidth: 2,
pointBorderColor: '#fff',
yAxisID: 'y1'
}]
};
const config = {
type: 'bar',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
title: {
display: true,
text: 'Quarterly Performance (Revenue, Cost & Profit Margin)',
font: {
size: 16,
weight: 'bold'
}
},
legend: {
display: true,
position: 'top',
labels: {
usePointStyle: true,
filter: function(item, chart) {
return true;
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#fff',
bodyColor: '#fff',
multiKeyBackground: 'rgba(255, 255, 255, 0.1)',
callbacks: {
label: function(context) {
if (context.datasetIndex === 2) {
return `${context.dataset.label}: ${context.parsed.y}%`;
} else {
return `${context.dataset.label}: $${context.parsed.y}M`;
}
},
afterBody: function(tooltipItems) {
const dataIndex = tooltipItems[0].dataIndex;
const revenue = tooltipItems.find(item => item.datasetIndex === 0)?.parsed.y || 0;
const cost = tooltipItems.find(item => item.datasetIndex === 1)?.parsed.y || 0;
const profit = revenue - cost;
return [`Net Profit: $${profit}M`];
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Quarter'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Amount ($M)'
},
grid: {
color: 'rgba(0, 0, 0, 0.1)'
},
beginAtZero: true
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Profit Margin (%)'
},
grid: {
drawOnChartArea: false
},
min: 0,
max: 50
}
},
animation: {
duration: 2000,
delay: (context) => {
let delay = 0;
if (context.type === 'data' && context.mode === 'default') {
delay = context.dataIndex * 200 + context.datasetIndex * 100;
}
return delay;
}
}
}
};
this.chart = new Chart(this.ctx, config);
}
addQuarter(quarter, revenue, cost, profitMargin) {
this.chart.data.labels.push(quarter);
this.chart.data.datasets[0].data.push(revenue);
this.chart.data.datasets[1].data.push(cost);
this.chart.data.datasets[2].data.push(profitMargin);
this.chart.update();
}
updateQuarter(index, revenue, cost, profitMargin) {
if (index < this.chart.data.labels.length) {
this.chart.data.datasets[0].data[index] = revenue;
this.chart.data.datasets[1].data[index] = cost;
this.chart.data.datasets[2].data[index] = profitMargin;
this.chart.update();
}
}
generateRandomData() {
const quarters = this.chart.data.labels;
quarters.forEach((quarter, index) => {
const revenue = Math.floor(Math.random() * 100) + 100;
const cost = Math.floor(revenue * (0.6 + Math.random() * 0.2));
const profitMargin = ((revenue - cost) / revenue * 100);
this.updateQuarter(index, revenue, cost, profitMargin);
});
}
destroy() {
if (this.chart) {
this.chart.destroy();
}
}
}
// Usage example
const mixedChart = new MixedChart('mixedChart');
// Random data generation button
document.getElementById('randomizeButton').addEventListener('click', () => {
mixedChart.generateRandomData();
});
// Add new quarter data
document.getElementById('addQuarterButton').addEventListener('click', () => {
const quarterCount = mixedChart.chart.data.labels.length;
const revenue = Math.floor(Math.random() * 100) + 150;
const cost = Math.floor(revenue * 0.7);
const profitMargin = ((revenue - cost) / revenue * 100);
mixedChart.addQuarter(`Q${quarterCount + 1}`, revenue, cost, profitMargin);
});
Real-time Data Updates
class RealTimeChart {
constructor(canvasId) {
this.ctx = document.getElementById(canvasId).getContext('2d');
this.chart = null;
this.isRunning = false;
this.maxDataPoints = 20;
this.updateInterval = null;
this.createChart();
}
createChart() {
const initialData = Array.from({length: this.maxDataPoints}, (_, i) => ({
x: new Date(Date.now() - (this.maxDataPoints - 1 - i) * 1000),
y: Math.random() * 100
}));
const config = {
type: 'line',
data: {
datasets: [{
label: 'CPU Usage (%)',
data: initialData,
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 0,
borderWidth: 2
}, {
label: 'Memory Usage (%)',
data: initialData.map(point => ({
x: point.x,
y: Math.random() * 80 + 10
})),
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 0,
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'System Monitoring (Real-time)'
},
legend: {
display: true
}
},
scales: {
x: {
type: 'time',
time: {
displayFormats: {
second: 'HH:mm:ss'
},
tooltipFormat: 'HH:mm:ss'
},
title: {
display: true,
text: 'Time'
}
},
y: {
beginAtZero: true,
max: 100,
title: {
display: true,
text: 'Usage (%)'
}
}
},
animation: {
duration: 0
},
elements: {
point: {
radius: 0
}
}
}
};
this.chart = new Chart(this.ctx, config);
}
addDataPoint() {
const now = new Date();
const cpuUsage = Math.random() * 100;
const memoryUsage = Math.random() * 80 + 10;
// Add new data points
this.chart.data.datasets[0].data.push({
x: now,
y: cpuUsage
});
this.chart.data.datasets[1].data.push({
x: now,
y: memoryUsage
});
// Remove old data points
if (this.chart.data.datasets[0].data.length > this.maxDataPoints) {
this.chart.data.datasets[0].data.shift();
this.chart.data.datasets[1].data.shift();
}
// Update chart
this.chart.update('none');
// Display current values
this.displayCurrentValues(cpuUsage, memoryUsage);
}
displayCurrentValues(cpu, memory) {
const statusDiv = document.getElementById('status');
if (statusDiv) {
statusDiv.innerHTML = `
<div>CPU: ${cpu.toFixed(1)}%</div>
<div>Memory: ${memory.toFixed(1)}%</div>
<div>Updated: ${new Date().toLocaleTimeString()}</div>
`;
}
}
start() {
if (!this.isRunning) {
this.isRunning = true;
this.updateInterval = setInterval(() => {
this.addDataPoint();
}, 1000);
}
}
stop() {
if (this.isRunning) {
this.isRunning = false;
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
}
clear() {
this.chart.data.datasets[0].data = [];
this.chart.data.datasets[1].data = [];
this.chart.update();
}
setUpdateInterval(milliseconds) {
if (this.isRunning) {
this.stop();
setTimeout(() => {
this.start();
}, 100);
}
// Save for next start
this.updateIntervalMs = milliseconds;
}
exportData() {
const data = this.chart.data.datasets.map((dataset, index) => ({
label: dataset.label,
data: dataset.data.map(point => ({
time: point.x.toISOString(),
value: point.y.toFixed(2)
}))
}));
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'realtime_data.json';
a.click();
URL.revokeObjectURL(url);
}
destroy() {
this.stop();
if (this.chart) {
this.chart.destroy();
}
}
}
// Usage example
const realtimeChart = new RealTimeChart('realtimeChart');
// Control buttons
document.getElementById('startButton').addEventListener('click', () => {
realtimeChart.start();
});
document.getElementById('stopButton').addEventListener('click', () => {
realtimeChart.stop();
});
document.getElementById('clearButton').addEventListener('click', () => {
realtimeChart.clear();
});
document.getElementById('exportButton').addEventListener('click', () => {
realtimeChart.exportData();
});
// Update interval settings
document.getElementById('intervalSelect').addEventListener('change', (e) => {
const interval = parseInt(e.target.value);
realtimeChart.setUpdateInterval(interval);
});
Responsive Design and Plugins
class ResponsiveChartManager {
constructor() {
this.charts = new Map();
this.setupResizeHandler();
}
createResponsiveChart(canvasId, config) {
const ctx = document.getElementById(canvasId).getContext('2d');
// Enhanced responsive configuration
const responsiveConfig = {
...config,
options: {
...config.options,
responsive: true,
maintainAspectRatio: false,
plugins: {
...config.options?.plugins,
// Add custom plugin
customResponsive: {
onResize: this.handleChartResize.bind(this)
}
}
}
};
const chart = new Chart(ctx, responsiveConfig);
this.charts.set(canvasId, chart);
return chart;
}
handleChartResize(chart, size) {
console.log(`Chart ${chart.canvas.id} resized to ${size.width}x${size.height}`);
// Adjust settings based on screen size
if (size.width < 600) {
// Mobile settings
this.applyMobileSettings(chart);
} else if (size.width < 1024) {
// Tablet settings
this.applyTabletSettings(chart);
} else {
// Desktop settings
this.applyDesktopSettings(chart);
}
chart.update('none');
}
applyMobileSettings(chart) {
const options = chart.options;
// Reduce font sizes
if (options.plugins?.title) {
options.plugins.title.font = { size: 14 };
}
if (options.plugins?.legend) {
options.plugins.legend.position = 'bottom';
options.plugins.legend.labels.font = { size: 10 };
}
// Simplify axis labels
if (options.scales?.x?.title) {
options.scales.x.title.display = false;
}
if (options.scales?.y?.title) {
options.scales.y.title.display = false;
}
}
applyTabletSettings(chart) {
const options = chart.options;
if (options.plugins?.title) {
options.plugins.title.font = { size: 16 };
}
if (options.plugins?.legend) {
options.plugins.legend.position = 'top';
options.plugins.legend.labels.font = { size: 12 };
}
if (options.scales?.x?.title) {
options.scales.x.title.display = true;
}
if (options.scales?.y?.title) {
options.scales.y.title.display = true;
}
}
applyDesktopSettings(chart) {
const options = chart.options;
if (options.plugins?.title) {
options.plugins.title.font = { size: 18 };
}
if (options.plugins?.legend) {
options.plugins.legend.position = 'top';
options.plugins.legend.labels.font = { size: 14 };
}
}
setupResizeHandler() {
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
this.charts.forEach((chart, canvasId) => {
const canvas = document.getElementById(canvasId);
if (canvas) {
const rect = canvas.getBoundingClientRect();
this.handleChartResize(chart, {
width: rect.width,
height: rect.height
});
}
});
}, 250);
});
}
// Custom plugin: Gradient background
createGradientPlugin() {
return {
id: 'gradientBackground',
beforeDraw: (chart) => {
const { ctx, chartArea } = chart;
if (!chartArea) return;
ctx.save();
const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
gradient.addColorStop(0, 'rgba(54, 162, 235, 0.1)');
gradient.addColorStop(1, 'rgba(54, 162, 235, 0.05)');
ctx.fillStyle = gradient;
ctx.fillRect(chartArea.left, chartArea.top, chartArea.width, chartArea.height);
ctx.restore();
}
};
}
// Custom plugin: Watermark
createWatermarkPlugin(text = 'Chart.js Demo') {
return {
id: 'watermark',
afterDraw: (chart) => {
const { ctx, chartArea } = chart;
if (!chartArea) return;
ctx.save();
ctx.globalAlpha = 0.1;
ctx.font = 'bold 20px Arial';
ctx.fillStyle = '#999';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const centerX = chartArea.left + chartArea.width / 2;
const centerY = chartArea.top + chartArea.height / 2;
ctx.fillText(text, centerX, centerY);
ctx.restore();
}
};
}
// Destroy all charts
destroyAll() {
this.charts.forEach(chart => chart.destroy());
this.charts.clear();
}
// Get specific chart
getChart(canvasId) {
return this.charts.get(canvasId);
}
// Get all charts
getAllCharts() {
return Array.from(this.charts.values());
}
}
// Register plugins
Chart.register(
// Register custom plugin globally
{
id: 'customResponsive',
resize: function(chart, size, options) {
if (options.onResize) {
options.onResize(chart, size);
}
}
}
);
// Usage example
const chartManager = new ResponsiveChartManager();
// Register gradient background plugin
Chart.register(chartManager.createGradientPlugin());
// Create responsive chart
const responsiveChart = chartManager.createResponsiveChart('responsiveChart', {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Dataset 1',
data: [65, 59, 80, 81, 56, 55],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
plugins: {
title: {
display: true,
text: 'Responsive Chart'
}
}
}
});
// Chart with watermark
const watermarkChart = chartManager.createResponsiveChart('watermarkChart', {
type: 'bar',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
label: 'Sample Data',
data: [12, 19, 3, 5],
backgroundColor: 'rgba(255, 99, 132, 0.8)'
}]
},
plugins: [chartManager.createWatermarkPlugin('My Company')]
});