Chart.js

Responsive chart library using Canvas elements. Provides 8 basic chart types, animations, and interaction features. Simple API design enables rapid data visualization.

JavaScriptData VisualizationChartsGraphsCanvasAnimationResponsive

GitHub Overview

chartjs/Chart.js

Simple HTML5 Charts using the <canvas> tag

Stars66,235
Watchers1,355
Forks11,960
Created:March 17, 2013
Language:JavaScript
License:MIT License

Topics

canvaschartgraphhtml5html5-chartsjavascript

Star History

chartjs/Chart.js Star History
Data as of: 7/19/2025, 02:41 AM

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

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')]
});