# Chart.js User Guide

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:

```html
<!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:

```html
<!-- 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

```bash
npm install chart.js
```

```bash
pnpm add chart.js
```

```bash
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:

```javascript
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:

```javascript
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.

```typescript
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

```javascript
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.

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:**

```javascript
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'`):

```javascript
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:**

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

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

```javascript
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:**

```javascript
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):

```javascript
datasets: [{
  tension: 0.4
}]
```

For Bezier interpolation that preserves monotonicity:

```javascript
datasets: [{
  cubicInterpolationMode: 'monotone'
}]
```

**Stepped lines:**

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

**Handling gaps** in data with `spanGaps`:

```javascript
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:

```javascript
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:**

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

**Doughnut chart:**

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

**Semi-circle doughnut:**

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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.

```javascript
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:

```javascript
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:

```javascript
// 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:

```javascript
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:

```javascript
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:

```javascript
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

```javascript
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

```javascript
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:**

```javascript
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:**

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

**Multiple axes:**

```javascript
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:

```javascript
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:

```bash
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:

```javascript
import 'chartjs-adapter-date-fns';
// or
import 'chartjs-adapter-luxon';
```

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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

```javascript
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:

```javascript
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

```javascript
// 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:**

```text
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:

```javascript
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:**

```text
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:

```bash
npm install chartjs-adapter-date-fns date-fns
```

```javascript
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:**

```text
Cannot determine type of 'x' axis.
```

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

**Solution:** Register the appropriate scale:

```javascript
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:**

```text
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:

```javascript
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:**

```html
<!-- 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:**

```text
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`:

```javascript
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:**

```text
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:

```javascript
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:**

```text
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:

```javascript
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:

```javascript
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:

```html
<div style="width: 100%; height: 400px;">
  <canvas id="myChart"></canvas>
</div>
```

```javascript
options: {
  responsive: true,
  maintainAspectRatio: false
}
```

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

```javascript
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`:

```bash
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:

```javascript
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/.

---

_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. Unofficial; not affiliated with or reviewed by the project._
