Jesse A. Green / notebook

Chart.js User Guide

Generated May 2026 from chartjs/Chart.js at ab79c3b

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Chart.js is a simple, flexible JavaScript charting library that renders beautiful charts on HTML5 canvas elements. It ships eight chart types out of the box, supports responsive design by default, and offers a tree-shakeable architecture so you only bundle what you need.

This guide covers Chart.js v4.5.1 and walks through everything from your first chart to advanced customization patterns.


1. Quick Start

Get a chart on screen in under two minutes using the CDN build.

1.1 Minimal Example

Create an HTML file with a <canvas> element and a <script> tag:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My First Chart</title>
</head>
<body>
  <canvas id="myChart" width="400" height="200"></canvas>

  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
  <script>
    const ctx = document.getElementById('myChart');
    new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
        datasets: [{
          label: 'Sales',
          data: [12, 19, 3, 5, 14],
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          y: { beginAtZero: true }
        }
      }
    });
  </script>
</body>
</html>

Open the file in any modern browser and you will see a bar chart with five data points. The Chart constructor accepts a canvas element, a canvas ID string, or a 2D rendering context.

1.2 What Just Happened

The UMD bundle (chart.umd.min.js) auto-registers every built-in controller, element, scale, and plugin. You called new Chart(element, config) and Chart.js:

  1. Created a bar controller with a category x-axis and a linear y-axis
  2. Applied default styling (the built-in Colors plugin assigned a blue palette)
  3. Made the chart responsive via ResizeObserver
  4. Animated everything in over 1000ms with easeOutQuart easing

Tip

For production applications, use the tree-shakeable ESM import (covered in section 2.3) instead of the UMD bundle. This can cut your Chart.js bundle size significantly.


2. Installation

2.1 CDN (No Build Step)

Use jsDelivr or unpkg to load the UMD bundle directly:

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>

<!-- unpkg -->
<script src="https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js"></script>

This registers all chart types and plugins automatically. No further setup is needed.

2.2 npm / pnpm / yarn

npm install chart.js
pnpm add chart.js
yarn add chart.js

Chart.js has a single runtime dependency: @kurkle/color (^0.3.0), which handles color parsing and manipulation. It installs automatically.

2.3 ESM Imports (Tree-Shakeable)

For the smallest bundle, import only what you need and register it explicitly:

import {
  Chart,
  BarController,
  CategoryScale,
  LinearScale,
  BarElement,
  Legend,
  Tooltip
} from 'chart.js';

Chart.register(
  BarController,
  CategoryScale,
  LinearScale,
  BarElement,
  Legend,
  Tooltip
);

If you prefer convenience over bundle size, import the auto-registering entry point:

import Chart from 'chart.js/auto';

This registers all built-in controllers, elements, scales, and plugins in one line.

Important

When using tree-shaking, you must register every component your chart uses. A bar chart needs BarController, BarElement, CategoryScale, and LinearScale at minimum. Forgetting a component will produce a runtime error like "Cannot determine type of '...' axis."

2.4 Package Exports

Chart.js provides three entry points:

Import Path Description
chart.js Tree-shakeable ESM (dist/chart.js) and CJS (dist/chart.cjs)
chart.js/auto Auto-registers all built-ins
chart.js/helpers Utility functions for plugin and extension authors

2.5 TypeScript

Type definitions ship with the package at dist/types.d.ts. No separate @types package is needed.

import { Chart, ChartConfiguration } from 'chart.js';

const config: ChartConfiguration<'bar'> = {
  type: 'bar',
  data: {
    labels: ['A', 'B', 'C'],
    datasets: [{
      label: 'Values',
      data: [10, 20, 30]
    }]
  }
};

const chart = new Chart(document.getElementById('myChart') as HTMLCanvasElement, config);

2.6 Requirements

  • A modern browser with HTML5 Canvas support (all current major browsers)
  • For time-based axes, an external date adapter is required (see section 5.8)

3. Configuration

Every chart starts with a configuration object containing type, data, and options.

3.1 Configuration Structure

const config = {
  type: 'line',          // Chart type string
  data: {
    labels: [...],       // x-axis labels
    datasets: [{
      label: 'Series 1',
      data: [...],
      // dataset-level options
    }]
  },
  options: {
    responsive: true,    // Chart-level options
    plugins: { ... },    // Plugin configuration
    scales: { ... },     // Scale configuration
    animation: { ... },  // Animation settings
    interaction: { ... } // Hover/click behavior
  }
};

3.2 Responsive Behavior

Charts are responsive by default. Chart.js uses ResizeObserver to detect container size changes and redraws automatically.

options: {
  responsive: true,           // Default: true
  maintainAspectRatio: true,  // Default: true
  aspectRatio: 2              // Width/height ratio (default: 2 for most, 1 for radial)
}

Note

The canvas element must be wrapped in a dedicated container <div>. Chart.js sets the canvas dimensions based on the container size. If the canvas is not inside a block-level container, responsive resizing may behave unexpectedly.

3.3 Animation

The default animation runs for 1000ms with easeOutQuart easing. You can customize or disable it:

options: {
  animation: {
    duration: 1000,       // Default: 1000ms
    easing: 'easeOutQuart' // Default easing
  }
}

Built-in transitions have their own defaults:

Transition Duration Purpose
active 400ms Hover state changes
resize 0ms Container resize (instant)
show default Dataset shown
hide default Dataset hidden

To disable all animation:

options: {
  animation: false
}

3.4 Scriptable Options

Most styling options accept a function instead of a static value. The function receives a context object and returns the value to apply:

datasets: [{
  label: 'Revenue',
  data: [65, 59, 80, 81, 56],
  backgroundColor: (ctx) => {
    const value = ctx.dataset.data[ctx.dataIndex];
    return value > 70 ? '#22c55e' : '#3b82f6';
  }
}]

The context object (ctx) includes properties like chart, dataset, dataIndex, datasetIndex, and parsed depending on where the option is used.

3.5 Indexable Options

Many styling options accept arrays, applying each value to the corresponding data point:

datasets: [{
  label: 'Scores',
  data: [65, 59, 80, 81, 56],
  backgroundColor: [
    '#ef4444', '#f97316', '#22c55e', '#22c55e', '#f97316'
  ]
}]

3.6 Color Handling

The built-in Colors plugin auto-assigns colors from a 7-color palette when you do not specify backgroundColor or borderColor:

Index Color
0 Blue
1 Red
2 Orange
3 Yellow
4 Green
5 Purple
6 Grey

Colors cycle for datasets beyond index 6. To override auto-coloring globally:

options: {
  plugins: {
    colors: {
      enabled: true,         // Default: true
      forceOverride: false   // Set true to override even manually set colors
    }
  }
}

3.7 Interaction Modes

Control how the chart responds to mouse hover and click events:

options: {
  interaction: {
    mode: 'index',      // 'point', 'nearest', 'index', 'dataset', 'x', 'y'
    intersect: true,     // Must hover directly over element
    axis: 'x'           // Axis to use for distance calculation
  }
}
Mode Behavior
point Items at the exact intersection point
nearest Single nearest item
index All items at the same index
dataset All items in the same dataset
x All items at the same x value
y All items at the same y value

4. Core Usage

4.1 Bar Charts

Bar charts display data as vertical or horizontal rectangular bars using a category axis and a linear value axis.

Basic vertical bar chart:

new Chart(ctx, {
  type: 'bar',
  data: {
    labels: ['Q1', 'Q2', 'Q3', 'Q4'],
    datasets: [{
      label: 'Revenue ($K)',
      data: [120, 180, 95, 210],
      backgroundColor: '#3b82f6'
    }]
  },
  options: {
    scales: {
      y: { beginAtZero: true }
    }
  }
});

Horizontal bar chart (set indexAxis to 'y'):

new Chart(ctx, {
  type: 'bar',
  data: {
    labels: ['Alice', 'Bob', 'Carol'],
    datasets: [{
      label: 'Tasks Completed',
      data: [23, 17, 31],
      backgroundColor: '#8b5cf6'
    }]
  },
  options: {
    indexAxis: 'y'
  }
});

Stacked bar chart:

options: {
  scales: {
    x: { stacked: true },
    y: { stacked: true }
  }
}

Floating bars accept [start, end] arrays instead of single values:

datasets: [{
  label: 'Temperature Range',
  data: [[12, 24], [15, 28], [10, 22], [18, 30]]
}]

Bar-specific defaults:

Option Default Description
categoryPercentage 0.8 Width of category block as fraction of grid
barPercentage 0.9 Width of bar within category block
grouped true Group bars of different datasets
skipNull false Skip null data points
barThickness auto Fixed pixel width
maxBarThickness — Maximum pixel width
minBarLength — Minimum pixel length (for visibility)

4.2 Line Charts

Line charts connect data points with lines and optionally fill the area beneath.

Basic line chart:

new Chart(ctx, {
  type: 'line',
  data: {
    labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
    datasets: [{
      label: 'Visitors',
      data: [1200, 1900, 3000, 5000, 4200, 3100],
      borderColor: '#3b82f6',
      backgroundColor: 'rgba(59, 130, 246, 0.1)',
      fill: true,
      tension: 0.3
    }]
  }
});

Smooth curves use the tension option (0 = straight lines, 0.4 = smooth):

datasets: [{
  tension: 0.4
}]

For Bezier interpolation that preserves monotonicity:

datasets: [{
  cubicInterpolationMode: 'monotone'
}]

Stepped lines:

datasets: [{
  stepped: true    // or 'before', 'after', 'middle'
}]

Handling gaps in data with spanGaps:

datasets: [{
  data: [10, 20, null, null, 50, 60],
  spanGaps: true       // Connects across null values
  // spanGaps: 86400000 // Or max gap in ms for time axes
}]

Per-segment styling lets you style individual line segments differently:

datasets: [{
  segment: {
    borderColor: (ctx) => ctx.p0.parsed.y > ctx.p1.parsed.y ? '#ef4444' : '#22c55e',
    borderDash: (ctx) => ctx.p0.skip || ctx.p1.skip ? [6, 6] : undefined
  }
}]

Line-specific defaults:

Option Default Description
showLine true Draw the connecting line
spanGaps false Connect across null/NaN
fill false Fill area below line
tension 0 Bezier curve tension

4.3 Pie and Doughnut Charts

Pie and doughnut charts display data as proportional arc segments. The doughnut is a pie with a hole in the center.

Pie chart:

new Chart(ctx, {
  type: 'pie',
  data: {
    labels: ['Desktop', 'Mobile', 'Tablet'],
    datasets: [{
      data: [55, 35, 10],
      backgroundColor: ['#3b82f6', '#ef4444', '#f97316']
    }]
  }
});

Doughnut chart:

new Chart(ctx, {
  type: 'doughnut',
  data: {
    labels: ['Used', 'Free'],
    datasets: [{
      data: [73, 27],
      backgroundColor: ['#3b82f6', '#e5e7eb'],
      cutout: '70%'      // Default: '50%'
    }]
  }
});

Semi-circle doughnut:

options: {
  rotation: -90,
  circumference: 180
}

Doughnut/Pie defaults:

Option Default Description
cutout '50%' (doughnut), 0 (pie) Inner radius
rotation 0 Start angle in degrees
circumference 360 Sweep angle in degrees
radius '100%' Outer radius
spacing 0 Gap between arcs in pixels
spacingMode 'angular' How spacing is applied
animation.animateRotate true Animate arc rotation on load
animation.animateScale true Scale from center on load

4.4 Scatter Charts

Scatter charts plot individual data points on linear x and y axes.

new Chart(ctx, {
  type: 'scatter',
  data: {
    datasets: [{
      label: 'Height vs Weight',
      data: [
        { x: 165, y: 60 },
        { x: 170, y: 72 },
        { x: 175, y: 68 },
        { x: 180, y: 85 },
        { x: 160, y: 55 }
      ],
      backgroundColor: '#3b82f6'
    }]
  },
  options: {
    scales: {
      x: { title: { display: true, text: 'Height (cm)' } },
      y: { title: { display: true, text: 'Weight (kg)' } }
    }
  }
});

Scatter defaults to showLine: false and interaction mode 'point'. To connect scatter points with lines, set showLine: true on the dataset.

4.5 Bubble Charts

Bubble charts add a third dimension (radius) to scatter plots. Each data point is an object with x, y, and r properties.

new Chart(ctx, {
  type: 'bubble',
  data: {
    datasets: [{
      label: 'Market Cap',
      data: [
        { x: 20, y: 30, r: 15 },
        { x: 40, y: 10, r: 25 },
        { x: 15, y: 45, r: 10 },
        { x: 35, y: 25, r: 35 }
      ],
      backgroundColor: 'rgba(59, 130, 246, 0.5)'
    }]
  },
  options: {
    scales: {
      x: { title: { display: true, text: 'Risk' } },
      y: { title: { display: true, text: 'Return (%)' } }
    }
  }
});

The r value is in pixels, not scaled to the axis. Both x and y axes are linear.

4.6 Radar Charts

Radar charts plot multivariate data on radial axes from a central point, forming a polygon.

new Chart(ctx, {
  type: 'radar',
  data: {
    labels: ['Speed', 'Power', 'Defense', 'Stamina', 'Skill', 'Luck'],
    datasets: [{
      label: 'Player A',
      data: [85, 72, 90, 68, 78, 60],
      borderColor: '#3b82f6',
      backgroundColor: 'rgba(59, 130, 246, 0.2)'
    }, {
      label: 'Player B',
      data: [65, 88, 70, 85, 92, 75],
      borderColor: '#ef4444',
      backgroundColor: 'rgba(239, 68, 68, 0.2)'
    }]
  },
  options: {
    scales: {
      r: {
        beginAtZero: true,
        max: 100
      }
    }
  }
});

Radar charts default to showLine: true, fill: 'start', and loop: true (closing the polygon).

4.7 Polar Area Charts

Polar area charts are similar to pie charts, but each segment has equal angle and the radius encodes the value.

new Chart(ctx, {
  type: 'polarArea',
  data: {
    labels: ['Red', 'Green', 'Yellow', 'Grey', 'Blue'],
    datasets: [{
      data: [11, 16, 7, 3, 14],
      backgroundColor: [
        'rgba(239, 68, 68, 0.5)',
        'rgba(34, 197, 94, 0.5)',
        'rgba(234, 179, 8, 0.5)',
        'rgba(156, 163, 175, 0.5)',
        'rgba(59, 130, 246, 0.5)'
      ]
    }]
  }
});

Uses a radialLinear scale. Defaults: startAngle: 0, spacing: 0, spacingMode: 'proportional'.

4.8 Mixed Charts

Combine chart types on the same canvas by setting type per dataset:

new Chart(ctx, {
  type: 'bar',   // Default type
  data: {
    labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
    datasets: [{
      type: 'line',
      label: 'Trend',
      data: [10, 15, 20, 25, 30],
      borderColor: '#ef4444',
      fill: false,
      order: 0          // Draw on top
    }, {
      label: 'Sales',
      data: [12, 19, 8, 25, 22],
      backgroundColor: '#3b82f6',
      order: 1          // Draw behind
    }]
  }
});

Use the order property to control drawing order (lower values draw on top).


5. Common Tasks

5.1 Updating Chart Data

Call chart.update() after modifying data or options:

// Add a data point
chart.data.labels.push('Jun');
chart.data.datasets[0].data.push(42);
chart.update();

// Replace all data
chart.data.datasets[0].data = [5, 10, 15, 20, 25, 30];
chart.update();

// Update without animation
chart.update('none');

5.2 Destroying and Recreating Charts

You must destroy a chart before reusing its canvas:

chart.destroy();

// Now safe to create a new chart on the same canvas
const newChart = new Chart(ctx, newConfig);

Warning

Failing to destroy a chart before creating a new one on the same canvas produces: "Canvas is already in use. Chart with ID '...' must be destroyed before the canvas with ID '...' can be reused." Always call .destroy() first.

5.3 Customizing the Legend

The legend is a built-in plugin displayed at the top by default:

options: {
  plugins: {
    legend: {
      display: true,        // Default: true
      position: 'top',      // 'top', 'left', 'bottom', 'right'
      align: 'center',      // 'start', 'center', 'end'
      labels: {
        color: '#374151',
        font: { size: 14 },
        padding: 20,
        usePointStyle: true  // Use point shape instead of box
      }
    }
  }
}

Clicking a legend item toggles the dataset's visibility by default. To customize:

options: {
  plugins: {
    legend: {
      onClick: (event, legendItem, legend) => {
        // Custom toggle behavior
        const index = legendItem.datasetIndex;
        const chart = legend.chart;
        const meta = chart.getDatasetMeta(index);
        meta.hidden = !meta.hidden;
        chart.update();
      }
    }
  }
}

5.4 Adding Titles and Subtitles

options: {
  plugins: {
    title: {
      display: true,         // Default: false
      text: 'Monthly Revenue',
      font: { size: 18, weight: 'bold' },
      padding: { top: 10, bottom: 20 },
      position: 'top'        // Default: 'top'
    },
    subtitle: {
      display: true,
      text: 'FY 2026 — All regions',
      font: { size: 14 },
      padding: { bottom: 10 }
    }
  }
}

The title plugin has weight 2000 and the subtitle has weight 1500, which determines their ordering in the layout.

5.5 Customizing Tooltips

options: {
  plugins: {
    tooltip: {
      enabled: true,
      backgroundColor: 'rgba(0, 0, 0, 0.8)',
      titleFont: { size: 14 },
      bodyFont: { size: 12 },
      padding: 12,
      cornerRadius: 4,
      callbacks: {
        label: (context) => {
          const label = context.dataset.label || '';
          const value = context.parsed.y;
          return `${label}: $${value.toLocaleString()}`;
        }
      }
    }
  }
}

5.6 Configuring Scales

Linear scale with grid styling:

options: {
  scales: {
    y: {
      type: 'linear',
      beginAtZero: true,
      title: {
        display: true,
        text: 'Amount ($)'
      },
      grid: {
        color: 'rgba(0, 0, 0, 0.1)',
        drawBorder: false
      },
      ticks: {
        callback: (value) => '$' + value.toLocaleString()
      }
    }
  }
}

Logarithmic scale:

options: {
  scales: {
    y: {
      type: 'logarithmic',
      min: 1,
      max: 10000
    }
  }
}

Multiple axes:

options: {
  scales: {
    y1: {
      type: 'linear',
      position: 'left',
      title: { display: true, text: 'Revenue' }
    },
    y2: {
      type: 'linear',
      position: 'right',
      title: { display: true, text: 'Users' },
      grid: { drawOnChartArea: false }
    }
  }
}

Assign datasets to axes with yAxisID: 'y2'.

5.7 Configuring the Radial Scale

Radar and polar area charts use the radialLinear scale:

options: {
  scales: {
    r: {
      beginAtZero: true,
      max: 100,
      ticks: {
        stepSize: 20,
        backdropColor: 'transparent'
      },
      pointLabels: {
        font: { size: 14 }
      },
      angleLines: {
        color: 'rgba(0, 0, 0, 0.1)'
      },
      startAngle: 0
    }
  }
}

5.8 Using Time Axes

The time scale requires an external date adapter. Install one alongside Chart.js:

npm install chartjs-adapter-date-fns date-fns
# or
npm install chartjs-adapter-luxon luxon
# or
npm install chartjs-adapter-moment moment

Then import the adapter before creating your chart:

import 'chartjs-adapter-date-fns';
// or
import 'chartjs-adapter-luxon';
new Chart(ctx, {
  type: 'line',
  data: {
    datasets: [{
      label: 'Signups',
      data: [
        { x: '2026-01-01', y: 100 },
        { x: '2026-02-01', y: 250 },
        { x: '2026-03-01', y: 180 },
        { x: '2026-04-01', y: 320 }
      ]
    }]
  },
  options: {
    scales: {
      x: {
        type: 'time',
        time: {
          unit: 'month',
          displayFormats: {
            month: 'MMM yyyy'
          }
        }
      }
    }
  }
});

Caution

Without a date adapter installed and imported, time axes throw: "This method is not implemented: Check that a complete date adapter is provided." Chart.js does not bundle any date library — you must provide one.

Available time units: millisecond, second, minute, hour, day, week, month, quarter, year.

The timeseries scale type extends time and distributes data points evenly regardless of the time gaps between them.

5.9 Area Fills

Use the Filler plugin to fill the area between a dataset and a boundary:

datasets: [{
  label: 'Temperature',
  data: [15, 18, 22, 25, 21, 17],
  fill: true,                    // Fill to the x-axis
  backgroundColor: 'rgba(59, 130, 246, 0.2)',
  borderColor: '#3b82f6'
}]

Fill targets:

Value Fills to
true or 'origin' The origin (zero line)
'start' The bottom of the chart area
'end' The top of the chart area
0, 1, 2... Dataset at that index
'-1', '+1' Relative dataset index

5.10 Data Decimation for Large Datasets

When plotting thousands of points, enable the Decimation plugin to reduce visual clutter and improve performance:

options: {
  plugins: {
    decimation: {
      enabled: true,
      algorithm: 'lttb',     // 'min-max' (default) or 'lttb'
      samples: 500            // Target number of points for LTTB
    }
  },
  parsing: false,             // Required: parsing must be disabled
  scales: {
    x: {
      type: 'linear'          // Must use linear or time x-axis
    }
  }
}

Important

Decimation only works with line charts that have a linear or time x-axis and parsing: false. The data must be provided as {x, y} objects sorted by x value.

5.11 Custom Point Styles

The PointElement supports various built-in shapes:

datasets: [{
  pointStyle: 'circle',       // Default
  // Other options: 'cross', 'crossRot', 'dash', 'line', 'rect',
  //                'rectRounded', 'rectRot', 'star', 'triangle'
  pointRadius: 5,
  pointHoverRadius: 8,
  pointBorderWidth: 2,
  pointHitRadius: 10           // Invisible hit detection area
}]

You can also pass an Image object or a canvas element as pointStyle for custom icons.

5.12 Stacking Datasets

Enable stacking on the relevant scales:

new Chart(ctx, {
  type: 'bar',
  data: {
    labels: ['Jan', 'Feb', 'Mar'],
    datasets: [
      { label: 'Product A', data: [10, 20, 30], stack: 'combined' },
      { label: 'Product B', data: [20, 10, 25], stack: 'combined' },
      { label: 'Target',    data: [25, 25, 40], stack: 'target' }
    ]
  },
  options: {
    scales: {
      x: { stacked: true },
      y: { stacked: true }
    }
  }
});

Use the stack property on datasets to create separate stacking groups on the same chart.

5.13 Handling Click Events

const chart = new Chart(ctx, config);

ctx.canvas.addEventListener('click', (event) => {
  const elements = chart.getElementsAtEventForMode(
    event, 'nearest', { intersect: true }, false
  );

  if (elements.length > 0) {
    const { datasetIndex, index } = elements[0];
    const label = chart.data.labels[index];
    const value = chart.data.datasets[datasetIndex].data[index];
    console.log(`Clicked: ${label} = ${value}`);
  }
});

5.14 Writing Custom Plugins

Chart.js plugins hook into lifecycle events:

const customPlugin = {
  id: 'customBackground',
  beforeDraw: (chart) => {
    const { ctx, chartArea: { top, bottom, left, right } } = chart;
    ctx.save();
    ctx.fillStyle = '#f8fafc';
    ctx.fillRect(left, top, right - left, bottom - top);
    ctx.restore();
  }
};

new Chart(ctx, {
  type: 'line',
  data: { ... },
  plugins: [customPlugin],  // Register per-chart
  options: { ... }
});

Key lifecycle hooks: beforeInit, afterInit, beforeUpdate, afterUpdate, beforeDraw, afterDraw, beforeDatasetsDraw, afterDatasetsDraw, beforeEvent, afterEvent, beforeDestroy.

5.15 Exporting Charts as Images

// Get a base64-encoded PNG
const imageUrl = chart.toBase64Image();

// Create a download link
const link = document.createElement('a');
link.download = 'chart.png';
link.href = chart.toBase64Image();
link.click();

6. Troubleshooting

6.1 Canvas Already in Use

Error:

Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID 'myChart' can be reused.

Cause: Creating a second chart on a canvas that already has an active chart instance.

Solution: Destroy the existing chart before creating a new one:

if (chart) {
  chart.destroy();
}
chart = new Chart(ctx, newConfig);

Prevention: Store the chart instance in a variable accessible to your update/recreate logic. In frameworks like React or Vue, destroy the chart in your cleanup/unmount lifecycle method.


6.2 Missing Date Adapter

Error:

This method is not implemented: Check that a complete date adapter is provided.

Cause: Using a time or timeseries scale without installing and importing a date adapter.

Solution: Install a date adapter package and import it:

npm install chartjs-adapter-date-fns date-fns
import 'chartjs-adapter-date-fns';

Prevention: Always pair time-axis usage with a date adapter import at the top of your module.


6.3 Cannot Determine Axis Type

Error:

Cannot determine type of 'x' axis.

Cause: When using tree-shaken imports, the required scale was not registered.

Solution: Register the appropriate scale:

import { CategoryScale, LinearScale } from 'chart.js';
Chart.register(CategoryScale, LinearScale);

Prevention: When using tree-shaking, register all controllers, elements, and scales your chart needs. Use chart.js/auto during development, then switch to selective imports for production.


6.4 Unsupported Decimation Algorithm

Error:

Unsupported decimation algorithm '...'

Cause: Setting options.plugins.decimation.algorithm to a value other than 'min-max' or 'lttb'.

Solution: Use one of the two supported algorithms:

plugins: {
  decimation: {
    algorithm: 'min-max'  // or 'lttb'
  }
}

Prevention: Refer to the two valid values listed above.


6.5 Chart Does Not Resize

Cause: The canvas element is not inside a dedicated block-level container, or CSS constraints prevent the container from resizing.

Solution:

<!-- Wrap the canvas in a dedicated div -->
<div style="width: 100%; max-width: 800px;">
  <canvas id="myChart"></canvas>
</div>

Ensure responsive: true is set (it is by default). Do not set explicit width and height attributes on the canvas when using responsive mode — let the container control sizing.

Prevention: Always wrap <canvas> in a dedicated container <div> and control dimensions on the container, not the canvas.


6.6 Chart Not Rendering (Blank Canvas)

Cause: Multiple possible reasons — zero-size container, missing registration, or data format mismatch.

Solution checklist:

  1. Verify the container has non-zero width and height
  2. Verify all required components are registered (when tree-shaking)
  3. Verify data format matches the chart type (e.g., bubble requires {x, y, r} objects)
  4. Check the browser console for errors

Prevention: Start with a working example from this guide and modify incrementally.


6.7 TypeError: Cannot Read Properties of Null

Error:

TypeError: Cannot read properties of null (reading 'getLabelAndValue')

Cause: A dataset references a scale or configuration that does not exist or has been misconfigured. This often happens with mixed chart types or when yAxisID points to a nonexistent scale.

Solution: Verify that every yAxisID and xAxisID in your datasets matches a key in options.scales:

datasets: [{
  yAxisID: 'y2',  // Must match a key in options.scales
  data: [...]
}],
options: {
  scales: {
    y2: { type: 'linear', position: 'right' }
  }
}

Prevention: Double-check axis ID strings for typos. Use TypeScript for compile-time checking of configuration shapes.


6.8 No Dataset Found at Index

Error:

No dataset found at index ...

Cause: Attempting to access a dataset by index that does not exist, typically from stale references after data updates.

Solution: Verify the dataset index is within bounds before accessing:

if (chart.data.datasets.length > datasetIndex) {
  const dataset = chart.data.datasets[datasetIndex];
}

Prevention: When dynamically adding or removing datasets, always call chart.update() and refresh any cached indices.


6.9 Recursion Detected

Error:

Recursion detected: ...

Cause: A circular reference in the configuration — typically a scriptable option that triggers its own resolution.

Solution: Review scriptable option functions to ensure they do not read the same option they are computing. Avoid referencing ctx.element.options from within a scriptable option resolver for that same property.

Prevention: Keep scriptable option functions focused on reading from ctx.dataset.data, ctx.dataIndex, or ctx.parsed, not from resolved element options.


6.10 PolarArea Chart Shows Nothing When Data Is Zero

Cause: Polar area charts compute arc radius from the data value. When all values are zero, all arcs have zero radius and nothing is visible.

Solution: Filter out or replace zero values before charting, or set a min on the radial scale:

options: {
  scales: {
    r: { min: 0, suggestedMax: 10 }
  }
}

Prevention: Validate incoming data and handle the all-zeros edge case in your application logic.


6.11 Doughnut Arc Spacing Looks Inconsistent

Cause: The spacing property on doughnut charts applies gaps between arcs, but the visual result can appear uneven with very small or very large segments.

Solution: Use spacingMode: 'angular' (the default) for consistent angular gaps, or try adjusting the spacing value. For very small segments, consider merging them into an "Other" category.

Prevention: Preview charts with representative data including edge cases (very small and very large segments) during development.


6.12 Animations Janky After Resize

Cause: The resize transition defaults to 0ms (instant). If your layout triggers frequent resizes during animation, the chart may appear to flicker.

Solution: Debounce container size changes, or set a resize transition duration:

options: {
  transitions: {
    resize: {
      animation: { duration: 200 }
    }
  }
}

Prevention: Avoid CSS transitions on the chart container that continuously change its size.


7. FAQ

Q: What browsers does Chart.js support? A: Chart.js works in all modern browsers that support HTML5 Canvas and ES6. This includes current versions of Chrome, Firefox, Safari, and Edge. Internet Explorer is not supported.

Q: How do I use Chart.js with React / Vue / Angular? A: Use a wrapper library (e.g., react-chartjs-2 for React, vue-chartjs for Vue). These handle component lifecycle (creation, updates, destruction) automatically. Alternatively, create the chart in a useEffect / onMounted hook and destroy it in the cleanup function.

Q: Can I have multiple charts on the same page? A: Yes. Each chart needs its own <canvas> element. Create separate new Chart() instances for each canvas.

Q: How do I change a chart's type after creation? A: You cannot change a chart's type in place. Destroy the chart and create a new one with the desired type.

Q: How do I make the chart fill its container? A: Ensure responsive: true (default) and maintainAspectRatio: false, then set the container's CSS width and height:

<div style="width: 100%; height: 400px;">
  <canvas id="myChart"></canvas>
</div>
options: {
  responsive: true,
  maintainAspectRatio: false
}

Q: How do I format tick labels (currency, percentage, etc.)? A: Use the ticks.callback function on the scale:

scales: {
  y: {
    ticks: {
      callback: (value) => value + '%'
    }
  }
}

Q: How do I show data labels directly on the chart? A: Chart.js does not include a built-in data label plugin. Use the community plugin chartjs-plugin-datalabels:

npm install chartjs-plugin-datalabels

Q: Can I animate dataset changes? A: Yes. When you modify data and call chart.update(), Chart.js automatically animates the transition. Pass 'none' to skip animation: chart.update('none').

Q: Why is my chart blurry on high-DPI (Retina) screens? A: Chart.js handles device pixel ratio automatically. If your chart appears blurry, ensure you are not setting the canvas width/height via CSS (which stretches the bitmap). Let Chart.js and responsive mode handle sizing through the container.

Q: What is the difference between chart.js and chart.js/auto? A: chart.js exports all components individually for tree-shaking — you import and register only what you need. chart.js/auto imports everything and registers it automatically, giving you the same behavior as the UMD bundle but as an ES module.

Q: How large is Chart.js? A: The full UMD bundle (chart.umd.min.js) is approximately 200KB minified. With tree-shaking, a single chart type can be significantly smaller depending on which components you register.

Q: Can I use Chart.js without npm (plain HTML)? A: Yes. Use the CDN link as shown in section 1.1. The UMD bundle works in a plain <script> tag with no build tools.

Q: How do I handle real-time / streaming data? A: Push new data points to the dataset's data array and new labels to labels, then call chart.update(). To keep a sliding window, also shift old data off the front:

chart.data.labels.push(newLabel);
chart.data.datasets[0].data.push(newValue);
if (chart.data.labels.length > 50) {
  chart.data.labels.shift();
  chart.data.datasets[0].data.shift();
}
chart.update();

Tip

For high-frequency streaming data (multiple updates per second), disable animations with animation: false and consider using the decimation plugin to keep rendering performant.

Q: Where can I find the full configuration reference? A: See the Admin Guide for full configuration reference. The official Chart.js documentation is also available at https://www.chartjs.org/docs/.