Skip to content

Chart.js Best Practices

Overview

Following best practices helps create effective, performant, and accessible charts with Chart.js.

Performance Optimization

Data Management

javascript
// Limit data points for better performance
const MAX_DATA_POINTS = 1000;

function optimizeData(data) {
    if (data.length > MAX_DATA_POINTS) {
        // Sample data points
        const step = Math.ceil(data.length / MAX_DATA_POINTS);
        return data.filter((_, index) => index % step === 0);
    }
    return data;
}

// Use optimized data
const optimizedData = optimizeData(rawData);

const chart = new Chart(ctx, {
    type: 'line',
    data: {
        labels: optimizedData.labels,
        datasets: [{
            data: optimizedData.values,
            // Disable animations for large datasets
            animation: {
                duration: optimizedData.length > 500 ? 0 : 1000
            }
        }]
    }
});

Chart Rendering Optimization

javascript
const optimizedChart = new Chart(ctx, {
    type: 'line',
    data: {
        // ... data
    },
    options: {
        // Disable animations for performance
        animation: false,
        
        // Disable hover effects for large datasets
        interaction: {
            intersect: false,
            mode: 'index'
        },
        
        // Optimize rendering
        elements: {
            point: {
                radius: 0,  // Hide points for large datasets
                hoverRadius: 5
            },
            line: {
                tension: 0.1,  // Reduce curve complexity
                borderWidth: 1
            }
        },
        
        // Responsive settings
        responsive: true,
        maintainAspectRatio: false,
        
        // Performance plugins
        plugins: {
            decimation: {
                enabled: true,
                algorithm: 'lttb',  // Largest Triangle Three Buckets
                samples: 1000,
                threshold: 1000
            }
        }
    }
});

Accessibility

ARIA Labels and Descriptions

javascript
const accessibleChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ['Product A', 'Product B', 'Product C'],
        datasets: [{
            label: 'Sales Data',
            data: [120, 190, 80],
            backgroundColor: [
                'rgba(255, 99, 132, 0.8)',
                'rgba(54, 162, 235, 0.8)',
                'rgba(255, 205, 86, 0.8)'
            ]
        }]
    },
    options: {
        plugins: {
            // Custom tooltip for accessibility
            tooltip: {
                callbacks: {
                    label: function(context) {
                        const label = context.dataset.label || '';
                        const value = context.parsed.y;
                        const total = context.dataset.data.reduce((a, b) => a + b, 0);
                        const percentage = ((value / total) * 100).toFixed(1);
                        
                        return `${label}: ${value} (${percentage}%)`;
                    }
                }
            },
            
            // Accessibility plugin
            legend: {
                display: true,
                position: 'top',
                labels: {
                    generateLabels: function(chart) {
                        const data = chart.data;
                        return data.labels.map((label, i) => {
                            const value = data.datasets[0].data[i];
                            return {
                                text: `${label}: ${value}`,
                                fillStyle: data.datasets[0].backgroundColor[i],
                                hidden: false,
                                index: i
                            };
                        });
                    }
                }
            }
        }
    }
});

// Add ARIA attributes
document.getElementById('chartCanvas').setAttribute('role', 'img');
document.getElementById('chartCanvas').setAttribute('aria-label', 'Sales data bar chart showing product performance');

Responsive Design

Mobile-First Approach

javascript
const responsiveChart = new Chart(ctx, {
    type: 'line',
    data: {
        // ... data
    },
    options: {
        responsive: true,
        maintainAspectRatio: false,
        
        // Breakpoint-specific configurations
        plugins: {
            legend: {
                display: true,
                position: window.innerWidth < 768 ? 'bottom' : 'top',
                labels: {
                    boxWidth: window.innerWidth < 768 ? 10 : 15,
                    padding: window.innerWidth < 768 ? 10 : 20,
                    font: {
                        size: window.innerWidth < 768 ? 10 : 12
                    }
                }
            }
        },
        
        scales: {
            x: {
                ticks: {
                    maxTicksLimit: window.innerWidth < 768 ? 6 : 12,
                    maxRotation: window.innerWidth < 768 ? 45 : 0,
                    minRotation: window.innerWidth < 768 ? 45 : 0,
                    font: {
                        size: window.innerWidth < 768 ? 10 : 12
                    }
                }
            },
            y: {
                ticks: {
                    maxTicksLimit: window.innerWidth < 768 ? 6 : 8,
                    font: {
                        size: window.innerWidth < 768 ? 10 : 12
                    }
                }
            }
        }
    }
});

// Handle window resize
window.addEventListener('resize', () => {
    responsiveChart.options.plugins.legend.position = window.innerWidth < 768 ? 'bottom' : 'top';
    responsiveChart.update();
});

Data Visualization Best Practices

Choose the Right Chart Type

javascript
// Data-driven chart type selection
function selectChartType(data, purpose) {
    const dataPoints = data.labels.length;
    const dataSeries = data.datasets.length;
    
    switch (purpose) {
        case 'trend':
            return 'line';
        case 'comparison':
            return dataPoints <= 10 ? 'bar' : 'line';
        case 'composition':
            return dataSeries === 1 ? 'pie' : 'doughnut';
        case 'distribution':
            return 'scatter';
        case 'relationship':
            return 'bubble';
        default:
            return 'bar';
    }
}

// Usage
const chartType = selectChartType(chartData, 'comparison');

Color Schemes

javascript
// Accessible color palettes
const colorPalettes = {
    // Colorblind-friendly palette
    accessible: [
        '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
        '#9467bd', '#8c564b', '#e377c2', '#7f7f7f'
    ],
    
    // Sequential palette for single-series data
    sequential: [
        '#f7fbff', '#deebf7', '#c6dbef', '#9ecae1',
        '#6baed6', '#4292c6', '#2171b5', '#08519c'
    ],
    
    // Diverging palette
    diverging: [
        '#67001f', '#b2182b', '#d6604d', '#f4a582',
        '#fddbc7', '#f7f7f7', '#d1e5f0', '#92c5de',
        '#4393c3', '#2166ac', '#053061'
    ]
};

function getColors(count, palette = 'accessible') {
    const colors = colorPalettes[palette];
    return Array.from({ length: count }, (_, i) => colors[i % colors.length]);
}

Error Handling

Robust Chart Creation

javascript
function createChartSafely(canvasId, config) {
    try {
        const canvas = document.getElementById(canvasId);
        if (!canvas) {
            throw new Error(`Canvas element with id '${canvasId}' not found`);
        }
        
        const ctx = canvas.getContext('2d');
        if (!ctx) {
            throw new Error('Unable to get 2D context from canvas');
        }
        
        // Validate data
        if (!config.data || !config.data.labels || !config.data.datasets) {
            throw new Error('Invalid chart data configuration');
        }
        
        // Validate datasets
        config.data.datasets.forEach((dataset, index) => {
            if (!dataset.data || !Array.isArray(dataset.data)) {
                throw new Error(`Dataset ${index} has invalid data`);
            }
        });
        
        return new Chart(ctx, config);
        
    } catch (error) {
        console.error('Chart creation failed:', error);
        
        // Show error message to user
        const errorElement = document.getElementById(canvasId);
        if (errorElement) {
            errorElement.innerHTML = `
                <div style="text-align: center; padding: 20px; color: #666;">
                    <p>Unable to display chart: ${error.message}</p>
                    <button onclick="location.reload()">Retry</button>
                </div>
            `;
        }
        
        return null;
    }
}

// Usage
const chart = createChartSafely('myChart', chartConfig);

Testing

Chart Testing Utilities

javascript
// Test utilities for Chart.js
class ChartTestUtils {
    static createMockCanvas() {
        const canvas = document.createElement('canvas');
        canvas.width = 400;
        canvas.height = 300;
        return canvas;
    }
    
    static createMockData(labels = ['A', 'B', 'C'], values = [10, 20, 30]) {
        return {
            labels: labels,
            datasets: [{
                label: 'Test Data',
                data: values,
                backgroundColor: 'rgba(54, 162, 235, 0.8)'
            }]
        };
    }
    
    static async waitForChartUpdate(chart, timeout = 1000) {
        return new Promise((resolve) => {
            const originalUpdate = chart.update;
            chart.update = function(...args) {
                const result = originalUpdate.apply(this, args);
                setTimeout(resolve, 100);  // Wait for animation
                return result;
            };
            
            setTimeout(resolve, timeout);  // Fallback timeout
        });
    }
    
    static getChartImage(chart) {
        return chart.toBase64Image();
    }
    
    static compareCharts(chart1, chart2) {
        const image1 = this.getChartImage(chart1);
        const image2 = this.getChartImage(chart2);
        return image1 === image2;
    }
}

// Example test
async function testChartCreation() {
    const canvas = ChartTestUtils.createMockCanvas();
    const ctx = canvas.getContext('2d');
    
    const mockData = ChartTestUtils.createMockData();
    const chart = new Chart(ctx, {
        type: 'bar',
        data: mockData
    });
    
    await ChartTestUtils.waitForChartUpdate(chart);
    
    console.log('Chart created successfully');
    return chart;
}

Content is for learning and research only.