# Chart.js Administrator & Developer Guide

Chart.js v4.5.1 is a canvas-based charting library for the browser and Node.js. This guide covers everything an administrator, integrator, or contributing developer needs to understand the internals, configure builds, deploy at scale, and troubleshoot production issues. See the User Guide for usage examples.

---

## 1. Architecture Overview

### 1.1 Design Philosophy

Chart.js is a modular, plugin-driven rendering engine that draws charts onto HTML5 `<canvas>` elements. The architecture follows a registry pattern where controllers, elements, scales, and plugins are registered at runtime. This enables tree-shaking for ESM consumers while providing a batteries-included UMD bundle for CDN usage.

### 1.2 Core Subsystems

The engine is organized into seven subsystems, each with a well-defined responsibility:

| Subsystem | Location | Responsibility |
|-----------|----------|----------------|
| **Chart Controller** | `src/core/core.controller.js` | Main entry point; orchestrates lifecycle, binds events, manages datasets |
| **Config Resolver** | `src/core/core.config.js` | Scope-chain resolution, scriptable options, caching |
| **Registry** | `src/core/core.registry.js` | Component registration (controllers, elements, scales, plugins) |
| **Animation** | `src/core/core.animation.js`, `core.animations.js`, `core.animator.js` | RAF-based interpolation with 31 easing functions |
| **Layout Engine** | `src/core/core.layouts.js` | Box-model layout for titles, legends, scales, chart area |
| **Interaction** | `src/core/core.interaction.js` | Hit detection across six modes |
| **Platform** | `src/platform/` | Browser DOM or OffscreenCanvas/Node abstraction |

### 1.3 Component Flow

```
User Config
    │
    ▼
Config Resolver ──► Scope Chain (dataset → type overrides → defaults)
    │
    ▼
Registry ──► Resolves controllers, scales, elements, plugins by ID
    │
    ▼
Chart Controller
    ├── Layout Engine (positions boxes: title, legend, scales, chart area)
    ├── Dataset Controllers (bar, line, pie, etc.)
    │       └── Elements (Arc, Bar, Line, Point)
    ├── Scales (category, linear, log, time, radial)
    ├── Plugins (lifecycle hooks: beforeInit → ... → destroy)
    └── Animator (singleton RAF loop)
            └── Animation instances (from → to interpolation)
```

### 1.4 Class Hierarchy

| Class | Parent | Source | Purpose |
|-------|--------|--------|---------|
| `Chart` | -- | `src/core/core.controller.js` | Main chart instance (~1500 lines) |
| `Defaults` | -- | `src/core/core.defaults.js` | Global defaults store |
| `Registry` | -- | `src/core/core.registry.js` | Component registry |
| `Animator` | -- | `src/core/core.animator.js` | Singleton animation loop |
| `DateAdapterBase` | -- | `src/core/core.adapters.ts` | Abstract date adapter interface |
| `Legend` | `Element` | `src/plugins/plugin.legend.js` | Legend rendering |
| `Title` | `Element` | `src/plugins/plugin.title.js` | Title rendering |
| `Tooltip` | `Element` | `src/plugins/plugin.tooltip.js` | Tooltip rendering (~1100 lines) |
| `TimeSeriesScale` | `TimeScale` | `src/scales/scale.timeseries.js` | Evenly-spaced time axis |
| `simpleArc` | -- | `src/plugins/plugin.filler/simpleArc.js` | Arc fill helper |

### 1.5 Runtime Dependency

Chart.js has exactly one runtime dependency:

| Package | Version | Purpose |
|---------|---------|---------|
| `@kurkle/color` | ^0.3.0 | Color parsing, manipulation, and conversion |

All other `devDependencies` (Rollup, Karma, Jasmine, SWC, etc.) are build-time only.

> [!IMPORTANT]
> Chart.js has **zero CLI commands, zero REST API endpoints, zero server-side actions, and zero filters**. It is a pure client-side library. Do not look for backend configuration sections -- they do not exist.

---

## 2. System Requirements

### 2.1 Build Environment

| Requirement | Version | Notes |
|-------------|---------|-------|
| **Node.js** | 16+ | Required for build and test |
| **pnpm** | >=8 (pinned: 8.13.0) | `packageManager` field in `package.json` enforces this |
| **TypeScript** | Included in devDeps | For type declarations only; source is JS + TS mix |

### 2.2 Browser Support

Chart.js targets **ES2018** (per `.eslintrc.yml`) and transpiles to **ES2022** via SWC in the build step. Runtime requirements:

| Feature | Used By | Fallback |
|---------|---------|----------|
| `<canvas>` 2D context | Core rendering | None -- required |
| `ResizeObserver` | `DomPlatform` container resize detection | Manual `resize()` calls |
| `MutationObserver` | `DomPlatform` canvas attach/detach | None |
| `Intl.NumberFormat` | `helpers.intl.js` tick formatting | Basic `toFixed()` |
| `requestAnimationFrame` | `Animator` | Polyfillable via `helpers.extras.js` |
| `OffscreenCanvas` | `BasicPlatform` (SSR/worker) | Falls back to `DomPlatform` |

### 2.3 Node.js / SSR Requirements

For server-side rendering, Chart.js needs a canvas implementation (e.g., `canvas` or `@napi-rs/canvas`) that provides a 2D context. The `BasicPlatform` is auto-selected when `OffscreenCanvas` is available or the context is not a browser DOM element. The `BasicPlatform` disables animations automatically.

### 2.4 pnpm Workspace

The repository uses a pnpm workspace defined in `pnpm-workspace.yaml`:

```yaml
packages:
  - docs
  - test/integration/*
```

This means `pnpm install` at root handles all sub-packages, including the VuePress documentation site and integration test projects.

---

## 3. Configuration Reference

### 3.1 Config Resolution Chain

The config resolver (`src/core/core.config.js`) implements a scope-chain lookup. When Chart.js resolves an option value, it traverses scopes in this order:

1. **Dataset-level options** (highest priority)
2. **Chart type overrides** (`Chart.overrides[type]`)
3. **Global defaults** (`Chart.defaults`)
4. **Descriptors** (internal metadata)

Key resolver methods:

| Method | Purpose |
|--------|---------|
| `datasetScopeKeys(type)` | Returns scope keys for dataset options |
| `datasetElementScopeKeys(type, elementType)` | Scope keys for per-element options within a dataset |
| `pluginScopeKeys(plugin)` | Scope keys for plugin options |
| `chartOptionScopes()` | All scope keys for chart-level options |
| `_createResolver()` | Builds a proxy-based resolver for a scope chain |
| `_attachContext()` | Wraps a resolver with scriptable option evaluation |

> [!NOTE]
> The config resolver uses aggressive caching for scope keys and resolvers. If you modify `Chart.defaults` after chart creation, call `chart.update()` to force re-resolution. Mutations to nested default objects are picked up automatically through the proxy mechanism, but adding entirely new scope branches requires a full update cycle.

### 3.2 Scale Auto-Detection

The config system auto-detects axis type from the scale ID or its `position` property:

- ID starting with `x` or position `top`/`bottom` maps to the x-axis
- ID starting with `y` or position `left`/`right` maps to the y-axis
- ID `r` maps to the radial axis

Scale defaults are then merged per chart type, so a `bar` chart's x-axis gets `CategoryScale` defaults while a `scatter` chart's x-axis gets `LinearScale` defaults.

### 3.3 Animation Configuration

| Property | Default | Description |
|----------|---------|-------------|
| `animation.duration` | `1000` | Base animation duration (ms) |
| `animation.easing` | `'easeOutQuart'` | Easing function name |
| `animation.delay` | `undefined` | Delay before animation start |
| `transitions.active.animation.duration` | `400` | Hover animation speed |
| `transitions.resize.animation.duration` | `0` | Instant on resize |
| `transitions.show.animations.colors.from` | `'transparent'` | Fade in from transparent |
| `transitions.hide.animations.colors.to` | `'transparent'` | Fade out to transparent |
| `resizeDelay` | `0` | Debounce delay for resize events (ms) |

Available easing functions (31 total in `helpers.easing.js`): `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeInOutQuart`, `easeInQuint`, `easeOutQuint`, `easeInOutQuint`, `easeInSine`, `easeOutSine`, `easeInOutSine`, `easeInExpo`, `easeOutExpo`, `easeInOutExpo`, `easeInCirc`, `easeOutCirc`, `easeInOutCirc`, `easeInElastic`, `easeOutElastic`, `easeInOutElastic`, `easeInBack`, `easeOutBack`, `easeInOutBack`, `easeInBounce`, `easeOutBounce`, `easeInOutBounce`.

### 3.4 Colors Plugin Palette

The built-in `ColorsPlugin` (`src/plugins/plugin.colors.ts`) assigns default colors when the user has not specified any:

| Index | Color (R,G,B) | Visual |
|-------|---------------|--------|
| 0 | 54, 162, 235 | Blue |
| 1 | 255, 99, 132 | Red |
| 2 | 255, 159, 64 | Orange |
| 3 | 255, 205, 86 | Yellow |
| 4 | 75, 192, 192 | Green |
| 5 | 153, 102, 255 | Purple |
| 6 | 201, 203, 207 | Grey |

Background colors are the same with 0.5 opacity. For Doughnut and PolarArea charts, colors rotate per data point (slice). For all other chart types, colors apply per dataset. The plugin skips assignment if the user has already defined `backgroundColor` or `borderColor`, unless `forceOverride: true` is set.

### 3.5 Decimation Plugin Configuration

| Option | Default | Description |
|--------|---------|-------------|
| `enabled` | `false` | Enable data decimation |
| `algorithm` | `'min-max'` | Algorithm: `'min-max'` or `'lttb'` |
| `threshold` | `4 * chartWidth` | Minimum samples before decimation activates |
| `samples` | -- | Target sample count (LTTB only) |

> [!WARNING]
> The decimation plugin **only works** with line charts using a linear or time x-axis **and** `parsing: false`. If any of these conditions are not met, decimation silently does nothing. The plugin replaces `dataset.data` via a getter/setter, so the original data array is preserved internally.

### 3.6 Interaction Modes

| Mode | Behavior |
|------|----------|
| `'point'` | Returns elements directly under the mouse pointer |
| `'nearest'` | Returns the single closest element |
| `'index'` | Returns all elements sharing the same data index |
| `'dataset'` | Returns all elements from the hovered dataset |
| `'x'` | Returns all elements at the same x coordinate |
| `'y'` | Returns all elements at the same y coordinate |

### 3.7 Plugin Lifecycle Hooks

Plugins receive these hooks in order during the chart lifecycle:

```
install → start → beforeInit → afterInit →
  [per update cycle:]
    beforeUpdate → beforeElementsUpdate → beforeDatasetsUpdate →
      beforeDatasetUpdate → afterDatasetUpdate (per dataset) →
    afterDatasetsUpdate → afterUpdate →
    beforeLayout → afterLayout →
    beforeRender →
      beforeDraw →
        beforeDatasetsDraw →
          beforeDatasetDraw → afterDatasetDraw (per dataset) →
        afterDatasetsDraw →
      afterDraw →
    afterRender →
  [per event:]
    beforeEvent → afterEvent →
  resize →
  stop → uninstall → destroy
```

### 3.8 Fill Modes (Filler Plugin)

The filler plugin (`src/plugins/plugin.filler/`, 8 files, ~20K chars) supports these fill targets:

| Value | Behavior |
|-------|----------|
| Dataset index (number) | Fill to another dataset's line |
| `'origin'` | Fill to the axis origin (zero line) |
| `'start'` | Fill to the start of the axis |
| `'end'` | Fill to the end of the axis |
| `'stack'` | Fill as stacked area |
| `true` | Alias for `'origin'` |
| `false` | Disable fill |

---

## 4. Deployment & Scaling

### 4.1 Build System

Chart.js uses Rollup with SWC transpilation. The build produces four output variants from `rollup.config.js`:

| Output | Entry Point | Format | File |
|--------|-------------|--------|------|
| UMD (minified) | `src/index.umd.ts` | UMD | `dist/chart.umd.min.js` |
| UMD (readable) | `src/index.umd.ts` | UMD | `dist/chart.umd.js` |
| ESM | `src/index.ts` + `src/helpers/index.ts` | ESM | `dist/chart.js` + `dist/helpers.js` |
| CJS | `src/index.ts` + `src/helpers/index.ts` | CJS | `dist/chart.cjs` + `dist/helpers.cjs` |

Build plugins in use:

| Plugin | Purpose |
|--------|---------|
| `@rollup/plugin-json` | Import JSON files |
| `@rollup/plugin-node-resolve` | Resolve node_modules |
| `rollup-plugin-swc3` | Transpile TS/JS (target: es2022) |
| `rollup-plugin-terser` | Minify UMD build |
| `rollup-plugin-cleanup` | Remove comments from non-minified builds |

### 4.2 Package Exports

The `package.json` exports map provides three entry points:

```json
{
  "chart.js": {
    "import": "dist/chart.js",
    "require": "dist/chart.cjs"
  },
  "chart.js/auto": "auto-registers all built-in components",
  "chart.js/helpers": "dist/helpers.js / dist/helpers.cjs"
}
```

- **`chart.js`** -- Tree-shakeable ESM/CJS entry. Consumers must manually `Chart.register(...)` the components they need.
- **`chart.js/auto`** -- Auto-registers all controllers, elements, scales, and plugins. Convenient but prevents tree-shaking.
- **`chart.js/helpers`** -- Utility functions for plugin/extension authors.

Type definitions ship as `dist/types.d.ts`. The `sideEffects` field marks `auto` and UMD builds as having side effects (they auto-register components on import).

### 4.3 Reproducible Builds

The Rollup config reads the `SOURCE_DATE_EPOCH` environment variable for reproducible builds. Set this to a Unix timestamp to ensure deterministic output:

```bash
SOURCE_DATE_EPOCH=$(date +%s) pnpm build
```

### 4.4 CDN Deployment

The UMD bundle is designed for CDN delivery:

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

The UMD entry (`src/index.umd.ts`) auto-registers all built-in components, so no additional setup is needed for `<script>` tag usage.

> [!TIP]
> For production deployments, always pin a specific version (e.g., `chart.js@4.5.1`) rather than using `@latest`. Chart.js major and minor versions can change default behavior, scale types, or plugin APIs.

### 4.5 npm Scripts Reference

| Script | Command | Purpose |
|--------|---------|---------|
| `build` | `rollup -c && pnpm emitDeclarations` | Full production build + type declarations |
| `autobuild` | `rollup -c -w` | Watch mode for development |
| `dev` | `karma start ./karma.conf.cjs --auto-watch --no-single-run --browsers chrome --grep` | Interactive test runner |
| `test` | `pnpm lint && pnpm test-ci` | Full test suite |
| `test-ci` | `concurrently "pnpm:test-ci-*"` | CI test run (Karma + integration) |
| `test-ci-karma` | Karma with coverage | Browser tests with Istanbul |
| `test-ci-integration` | Integration suite | Node ESM/CJS, React, TypeScript |
| `lint` | `concurrently "pnpm:lint-*"` | All linters in parallel |
| `lint-js` | `eslint "src/**/*.{js,ts}" "test/**/*.js" "docs/**/*.js" --cache` | ESLint |
| `docs` | `pnpm run build && pnpm --filter "./docs/**" build` | Build docs site |

### 4.6 Tree-Shaking Guide

To minimize bundle size in modern bundlers (webpack, Rollup, Vite, esbuild):

1. Import from `chart.js` (not `chart.js/auto`)
2. Register only the components you use:

```javascript
import {
  Chart,
  LineController,
  LineElement,
  PointElement,
  LinearScale,
  CategoryScale,
  Tooltip,
  Legend
} from 'chart.js';

Chart.register(
  LineController,
  LineElement,
  PointElement,
  LinearScale,
  CategoryScale,
  Tooltip,
  Legend
);
```

3. The `sideEffects` field in `package.json` correctly marks `chart.js/auto` and UMD builds, so bundlers will not tree-shake those entry points.

---

## 5. Operations

### 5.1 Chart Instance Management

The `Chart` class maintains a singleton `instances` map keyed by a numeric chart ID:

```javascript
Chart.instances  // Map of all active chart instances
Chart.getChart(canvasOrId)  // Retrieve chart bound to a canvas element or ID string
```

> [!CAUTION]
> Chart.js **prevents reuse of a canvas** that already has a chart bound to it. You must call `chart.destroy()` before creating a new chart on the same canvas. Failure to do so throws an error and leaves the canvas in an inconsistent state. This is the single most common issue in SPA frameworks (React, Vue, Angular) where components remount.

### 5.2 Memory Management

Each `Chart` instance holds references to:
- The canvas element and its 2D rendering context
- All dataset data arrays
- Parsed data caches
- Animation state
- Event listener proxies on the DOM

**Cleanup checklist:**
1. Call `chart.destroy()` -- this removes event listeners, clears animations, and deletes the instance from `Chart.instances`
2. In SPA frameworks, destroy in the unmount/cleanup lifecycle hook
3. The decimation plugin replaces `dataset.data` with getter/setter proxies; `destroy()` cleans these up

### 5.3 Resize Behavior

The `DomPlatform` uses `ResizeObserver` to detect container size changes and `MutationObserver` for canvas attach/detach events. It also listens for `devicePixelRatio` changes (e.g., dragging a window between monitors with different DPIs).

| Config | Default | Effect |
|--------|---------|--------|
| `responsive` | `true` | Auto-resize to container |
| `maintainAspectRatio` | `true` | Preserve width/height ratio |
| `resizeDelay` | `0` | Debounce resize (ms) |

The internal `_doResize` method is debounced using the `resizeDelay` option. Set this to a non-zero value (e.g., `100`) if resize events cause excessive redraws.

### 5.4 Testing Infrastructure

**Browser tests**: Karma + Jasmine (`karma.conf.cjs`)
- Fixture-based visual regression tests in `test/fixtures/`
- Coverage via Istanbul when running `test-ci-karma`

**Integration tests** (in `test/integration/`):
| Test Suite | What It Validates |
|------------|-------------------|
| Node ESM | `import { Chart } from 'chart.js'` works in Node |
| Node CJS | `require('chart.js')` works in Node |
| React browser | Chart.js renders in a React app |
| TypeScript Node | Type declarations compile without errors |
| TypeScript NodeNext | Module resolution with `moduleResolution: NodeNext` |

### 5.5 Linting & Code Quality

**ESLint** (`.eslintrc.yml`):
- Extends the `chartjs` shared config
- Enforces **ES2018** language restrictions
- TypeScript support for `.ts` files
- Cache enabled for incremental linting

**Code Climate** (`.codeclimate.yml`):
- Duplication detection enabled
- FIXME/TODO comment detection
- Argument count threshold: 5
- Method complexity threshold: 7

---

## 6. Performance

### 6.1 Rendering Pipeline

Each `chart.update()` call triggers this pipeline:

1. **Config resolution** -- resolves all scriptable options against context
2. **Layout** -- positions boxes (title, legend, scales) using the box layout engine
3. **Scale update** -- tick generation, auto-skip, label sizing
4. **Dataset update** -- maps data to pixel positions, applies stacking
5. **Animation** -- creates/updates `Animation` instances for changed properties
6. **Render** -- draws to canvas via the `Animator` singleton's RAF loop

### 6.2 Data Decimation

For large datasets (thousands of points), the decimation plugin is critical:

**Min-Max algorithm** (default):
- Preserves local minima and maxima
- Fast O(n) single pass
- Good for data where peaks matter

**LTTB algorithm** (Largest Triangle Three Buckets):
- Better visual fidelity for general datasets
- O(n) complexity
- Configured via `samples` option

> [!TIP]
> Enable decimation for any line chart with more than a few hundred data points. Set `parsing: false` and use a linear or time x-axis. The threshold defaults to `4 * chartWidth` pixels, meaning decimation only activates when data points significantly outnumber pixels.

### 6.3 Animation Performance

The `Animator` is a singleton that runs a single `requestAnimationFrame` loop for all active chart instances. Optimizations:

- Animations that have completed are automatically cleaned up
- The loop stops entirely when no animations are active
- `transitions.resize.animation.duration = 0` ensures resize is instant
- Set `animation: false` globally to disable all animations (recommended for dashboards with frequent updates)

```javascript
// Disable animations for real-time dashboards
new Chart(ctx, {
  options: {
    animation: false,
    transitions: {
      active: { animation: { duration: 0 } }
    }
  }
});
```

### 6.4 Tick Auto-Skip

The auto-skip system (`src/core/core.scale.autoskip.js`) prevents label overlap on axes by calculating maximum fitting labels based on:

- Label rotation angle
- Available pixel space
- Font size and padding

This runs on every scale update. For extremely dense tick sets, auto-skip can become a bottleneck; consider setting explicit `ticks.maxTicksLimit` to cap computation.

### 6.5 Performance Checklist

| Technique | Impact | When to Use |
|-----------|--------|-------------|
| `animation: false` | High | Real-time/streaming data, >10 updates/sec |
| Decimation plugin | High | Line charts with >500 points |
| `parsing: false` + pre-formatted data | Medium | Large datasets; skip internal parsing |
| `resizeDelay: 100` | Medium | Frequent container resizes |
| Tree-shake imports | Medium | Bundle size reduction |
| `spanGaps: true` | Low | Skip null-point processing |

---

## 7. Security

### 7.1 Threat Model

Chart.js is a client-side rendering library. The primary security concerns are:

1. **Cross-Site Scripting (XSS) via data injection** -- Chart.js renders to `<canvas>`, which does not interpret HTML or execute scripts. Canvas drawing operations are inherently safe against XSS.
2. **Prototype pollution** -- The config resolution chain uses deep merging. Chart.js's `helpers.core.js` implements `isObject()` checks but consumers must sanitize untrusted config input.
3. **Denial of Service** -- Extremely large datasets or deeply nested configs can cause browser tab crashes. Apply data limits at the application layer.

> [!WARNING]
> While canvas rendering is XSS-safe, the **tooltip plugin** can render custom HTML via the `external` callback. If you use `external` tooltips that inject user-supplied data into the DOM, you **must** sanitize that data. The default tooltip uses canvas rendering and is safe.

### 7.2 Supply Chain

Chart.js has exactly one runtime dependency (`@kurkle/color`). The attack surface is minimal. The build toolchain (Rollup, SWC, Karma, etc.) runs at development time only and is not shipped to consumers.

### 7.3 Content Security Policy

For CSP-compliant environments:

- Chart.js does not use `eval()` or `new Function()`
- No inline styles are injected into the DOM
- Canvas operations do not require `unsafe-inline` or `unsafe-eval`
- Required CSP directives: none beyond the defaults

### 7.4 Data Validation

Chart.js does not validate incoming data types at the boundary. It expects numeric values where numbers are needed and trusts the caller. For applications accepting user-supplied chart data, validate at the application layer before passing to Chart.js.

---

## 8. Developer Internals

### 8.1 Chart Class Deep Dive

The `Chart` class (`src/core/core.controller.js`, ~1500 lines) is the central orchestrator.

**Constructor** accepts:
- An `HTMLCanvasElement`
- A string ID (looks up canvas by DOM ID)
- A `CanvasRenderingContext2D`
- A jQuery-like wrapper (extracts first element)

**Static members:**

| Member | Type | Purpose |
|--------|------|---------|
| `Chart.defaults` | `Defaults` | Global defaults store |
| `Chart.instances` | `Object` | Map of all active charts by ID |
| `Chart.overrides` | `Object` | Per-type option overrides |
| `Chart.registry` | `Registry` | Component registry |
| `Chart.version` | `string` | Library version string |
| `Chart.getChart(key)` | method | Find chart by canvas or ID |
| `Chart.register(...)` | method | Register components |
| `Chart.unregister(...)` | method | Unregister components |

**Instance methods:**

| Method | Purpose |
|--------|---------|
| `update(mode?)` | Full update cycle (config resolve → layout → render) |
| `render()` | Render only (no data/layout recalculation) |
| `resize(width?, height?)` | Manual resize |
| `destroy()` | Full cleanup (events, animations, DOM, instance map) |
| `clear()` | Clear canvas without destroying |
| `stop()` | Stop running animations |
| `toBase64Image(type?, quality?)` | Export chart as data URL |
| `getElementsAtEventForMode(e, mode, options, useFinalPosition)` | Hit detection |
| `getSortedVisibleDatasetMetas()` | Dataset metadata in draw order |
| `getDatasetMeta(index)` | Metadata for specific dataset |
| `isDatasetVisible(index)` | Visibility check |
| `show(datasetIndex)` / `hide(datasetIndex)` | Toggle dataset visibility |
| `toggleDataVisibility(index)` / `getDataVisibility(index)` | Toggle individual data point visibility |
| `notifyPlugins(hook, args?)` | Trigger plugin lifecycle hook |
| `data` (getter/setter) | Access chart data |
| `options` (getter/setter) | Access resolved options |
| `bindEvents()` | Attach DOM event listeners |

### 8.2 Registry Internals

The `Registry` class holds four `TypedRegistry` instances:

```
Registry
├── controllers  (TypedRegistry<DatasetController>)
├── elements     (TypedRegistry<Element>)
├── scales       (TypedRegistry<Scale>)
└── plugins      (TypedRegistry<Plugin>)
```

Each `TypedRegistry` enforces that registered components have a string `id` property. Registration fails with the error `"class does not have id: "` if this is missing.

**Lookup methods**: `getController(id)`, `getElement(id)`, `getScale(id)`, `getPlugin(id)`.

The `add(...)` and `remove(...)` methods accept varargs of individual components or arrays, enabling batch registration:

```javascript
Chart.register(BarController, BarElement, CategoryScale, LinearScale);
Chart.register([Tooltip, Legend, Title]);
```

### 8.3 Controllers (8 Chart Types)

| Controller | File | Element(s) Used |
|-----------|------|-----------------|
| `BarController` | `controller.bar.js` | `BarElement` |
| `BubbleController` | `controller.bubble.js` | `PointElement` |
| `DoughnutController` | `controller.doughnut.js` | `ArcElement` |
| `LineController` | `controller.line.js` | `LineElement`, `PointElement` |
| `PieController` | `controller.pie.js` | `ArcElement` |
| `PolarAreaController` | `controller.polarArea.js` | `ArcElement` |
| `RadarController` | `controller.radar.js` | `LineElement`, `PointElement` |
| `ScatterController` | `controller.scatter.js` | `PointElement` |

All controllers extend `DatasetController` (`src/core/core.datasetController.js`), which provides the base update/draw/hit-test cycle.

### 8.4 Scale System

**Hierarchy:**

```
Scale (core.scale.js)
├── CategoryScale (scale.category.js)
├── LinearScale (scale.linear.js)
│   └── extends LinearScaleBase (scale.linearbase.js)
├── LogarithmicScale (scale.logarithmic.js)
│   └── extends LinearScaleBase
├── RadialLinearScale (scale.radialLinear.js) — radar/polar with point labels
├── TimeScale (scale.time.js) — requires DateAdapter
│   └── TimeSeriesScale (scale.timeseries.js) — evenly-spaced variant
```

**Tick generation:**
- Linear scales use the **niceNum algorithm** for human-friendly intervals
- Logarithmic scales use log10-based tick placement
- Time scales delegate to a `DateAdapter` implementation

> [!IMPORTANT]
> The `TimeScale` and `TimeSeriesScale` require a `DateAdapter` implementation. Chart.js ships only the abstract `DateAdapterBase` (`src/core/core.adapters.ts`). You must install an adapter like `chartjs-adapter-date-fns`, `chartjs-adapter-luxon`, or `chartjs-adapter-moment`. Without one, any method call throws: `"This method is not implemented: Check that a complete date adapter is provided."`

### 8.5 Elements (4 Visual Primitives)

| Element | File | Used By |
|---------|------|---------|
| `ArcElement` | `elements/element.arc.js` | Doughnut, Pie, PolarArea |
| `BarElement` | `elements/element.bar.js` | Bar |
| `LineElement` | `elements/element.line.js` | Line, Radar |
| `PointElement` | `elements/element.point.js` | Line, Bubble, Scatter, Radar |

Elements are the lowest-level visual components. Each element knows how to draw itself on a canvas context and calculate its bounding box for hit detection.

### 8.6 Platform Abstraction

The platform layer (`src/platform/`) provides environment-specific functionality:

**Platform detection** (`_detectPlatform(canvas)`):
- If `canvas` is an `OffscreenCanvas` or not a DOM element: **BasicPlatform**
- Otherwise: **DomPlatform**

**DomPlatform** provides:
- `ResizeObserver` for container size monitoring
- `MutationObserver` for canvas DOM attachment changes
- `devicePixelRatio` change listener (multi-monitor support)
- Throttled event proxies for pointer/touch/mouse events
- Touch-to-mouse event mapping: `touchstart→mousedown`, `touchmove→mousemove`, `touchend→mouseup`, `pointerenter→mouseenter`

**BasicPlatform** provides:
- Minimal `acquireContext` that works with non-DOM canvases
- Animations disabled by default
- No event binding

### 8.7 Helpers Library (18 Modules)

The `src/helpers/` directory (100K chars) provides utility functions exported via `chart.js/helpers`:

| Module | Key Functions |
|--------|---------------|
| `helpers.canvas.js` | `drawPoint()`, `clipArea()`, `unclipArea()`, `renderText()` |
| `helpers.collection.js` | `_lookup()`, `_lookupByKey()`, `_rlookupByKey()` |
| `helpers.color.js` | Color parsing/conversion (wraps `@kurkle/color`) |
| `helpers.config.ts` | `_createResolver()`, `_attachContext()`, recursion detection |
| `helpers.core.js` | `isObject()`, `each()`, `merge()`, `uid()`, `valueOrDefault()` |
| `helpers.curve.js` | Bezier control point computation (monotone, spline) |
| `helpers.dom.js` | `getRelativePosition()`, `getMaximumSize()`, `readUsedSize()` |
| `helpers.easing.js` | 31 easing functions |
| `helpers.extras.js` | `requestAnimFrame()`, `throttled()`, `debounce()` |
| `helpers.interpolation.js` | `_steppedInterpolation()`, `_bezierInterpolation()` |
| `helpers.intl.js` | `formatNumber()` via `Intl.NumberFormat` |
| `helpers.math.js` | `toRadians()`, `toDegrees()`, `distanceBetweenPoints()`, `sign()` |
| `helpers.options.js` | `toFont()`, `toPadding()`, `toLineHeight()`, `resolve()` |
| `helpers.rtl.js` | RTL layout support utilities |
| `helpers.segment.js` | Line segment computation for fills and decimation |

### 8.8 Built-in Plugins (7)

| Plugin | File | Lines | Purpose |
|--------|------|-------|---------|
| Colors | `plugin.colors.ts` | ~80 | Auto-assigns the 7-color palette |
| Decimation | `plugin.decimation.js` | ~280 | LTTB or min-max data reduction |
| Filler | `plugin.filler/` (8 files) | ~800 | Area fills between datasets/axes |
| Legend | `plugin.legend.js` | ~500 | Interactive legend with click handlers |
| Subtitle | `plugin.subtitle.js` | ~30 | Secondary title below the main title |
| Title | `plugin.title.js` | ~200 | Chart title rendering |
| Tooltip | `plugin.tooltip.js` | ~1100 | Hover tooltips (canvas or external HTML) |

### 8.9 Writing Custom Plugins

A Chart.js plugin is an object with an `id` property and any subset of the lifecycle hooks:

```javascript
const myPlugin = {
  id: 'myPlugin',
  
  // Called once when plugin is registered
  install(chart, args, options) {},
  
  // Called when chart starts using this plugin
  start(chart, args, options) {},
  
  // Before/after each update cycle
  beforeUpdate(chart, args, options) {},
  afterUpdate(chart, args, options) {},
  
  // Before/after each draw
  beforeDraw(chart, args, options) {},
  afterDraw(chart, args, options) {},
  
  // Event handling
  beforeEvent(chart, args, options) {},
  afterEvent(chart, args, options) {},
  
  // Cleanup
  destroy(chart, args, options) {},
  uninstall(chart, args, options) {},
};

Chart.register(myPlugin);
```

Plugin options are resolved through the config scope chain at `options.plugins[pluginId]`.

### 8.10 Writing Custom Scales

Extend the `Scale` base class and register:

```javascript
class CustomScale extends Chart.Scale {
  static id = 'custom';
  static defaults = {};

  determineDataLimits() {
    // Set this.min and this.max
  }
  
  buildTicks() {
    // Return array of {value, label} tick objects
    return [];
  }
  
  getPixelForValue(value) {
    // Convert data value to pixel coordinate
  }
  
  getValueForPixel(pixel) {
    // Convert pixel coordinate to data value
  }
}

Chart.register(CustomScale);
```

---

## 9. Integration Points

### 9.1 Framework Integration Patterns

**React**: Use `useRef` for the canvas and `useEffect` for lifecycle:

```jsx
useEffect(() => {
  const chart = new Chart(canvasRef.current, config);
  return () => chart.destroy(); // Critical cleanup
}, []);
```

**Vue**: Use `onMounted`/`onBeforeUnmount`:

```javascript
onMounted(() => {
  chart = new Chart(canvasRef.value, config);
});
onBeforeUnmount(() => {
  chart?.destroy();
});
```

**Angular**: Use `ngAfterViewInit`/`ngOnDestroy` with `@ViewChild`.

> [!NOTE]
> In all SPA frameworks, the most critical requirement is calling `chart.destroy()` in the cleanup/unmount hook. Without this, you will see errors about canvas reuse, memory leaks from retained event listeners, and orphaned animation loops.

### 9.2 Server-Side Rendering

For generating chart images on the server:

1. Install a canvas implementation: `npm install canvas` (node-canvas) or `@napi-rs/canvas`
2. Import and create a canvas:

```javascript
import { createCanvas } from 'canvas';
import { Chart } from 'chart.js/auto';

const canvas = createCanvas(800, 400);
const chart = new Chart(canvas, { /* config */ });
const buffer = canvas.toBuffer('image/png');
```

The `BasicPlatform` is auto-selected and disables animations. No DOM is needed.

### 9.3 Web Worker Rendering

Chart.js can render in a Web Worker using `OffscreenCanvas`:

```javascript
// Main thread
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);

// Worker
self.onmessage = ({ data: { canvas } }) => {
  const chart = new Chart(canvas, config);
};
```

The `BasicPlatform` is auto-detected for `OffscreenCanvas` instances.

### 9.4 Date Adapter Integration

Time-based scales require a date adapter. The adapter must extend `DateAdapterBase` and implement:

| Method | Purpose |
|--------|---------|
| `init(options)` | Initialize with locale/timezone options |
| `formats()` | Return format strings for each time unit |
| `parse(value, format)` | Parse a value into a timestamp |
| `format(timestamp, format)` | Format a timestamp into a display string |
| `add(timestamp, amount, unit)` | Add time to a timestamp |
| `diff(a, b, unit)` | Difference between two timestamps |
| `startOf(timestamp, unit)` | Start of a time unit |
| `endOf(timestamp, unit)` | End of a time unit |

Popular adapters: `chartjs-adapter-date-fns`, `chartjs-adapter-luxon`, `chartjs-adapter-moment`, `chartjs-adapter-dayjs-4`.

### 9.5 TypeScript Integration

Type definitions are exported from `dist/types.d.ts`. Key types:

```typescript
import type {
  ChartConfiguration,
  ChartData,
  ChartOptions,
  ChartType,
  Plugin,
  Scale,
  Element,
  DatasetController
} from 'chart.js';
```

> [!TIP]
> The integration test suite validates TypeScript compatibility under both `moduleResolution: Node` and `moduleResolution: NodeNext`. If you encounter type resolution issues, ensure your `tsconfig.json` matches one of these tested configurations.

---

## 10. Troubleshooting

### 10.1 Error Reference

| Error Message | Source | Cause | Solution |
|---------------|--------|-------|----------|
| `"Recursion detected: "` | `helpers.config.ts:263` | Circular reference in scriptable option evaluation | Check that scriptable options do not reference themselves through the context chain |
| `"This method is not implemented: Check that a complete date adapter is provided."` | `core.adapters.ts:66` | Time scale used without a date adapter | Install and import a date adapter (e.g., `chartjs-adapter-date-fns`) |
| `"Cannot determine type of "` | `core.config.js:52` | Scale configuration has an unrecognizable type | Ensure the scale's `type` matches a registered scale ID |
| `"No dataset found at index "` | `core.controller.js:1101` | Referencing a dataset index that does not exist | Check `data.datasets.length` before accessing by index |
| `"class does not have id: "` | `core.typedRegistry.js:38` | Registering a component without a static `id` | Add `static id = 'myComponent'` to your class |
| `"Unsupported decimation algorithm "` | `plugin.decimation.js:277` | Invalid value for `plugins.decimation.algorithm` | Use `'min-max'` or `'lttb'` only |
| `"Cannot find a dataset at index "` | `plugin.tooltip.js:1112` | Tooltip references a non-existent dataset | Usually caused by data mutation during tooltip render; ensure atomic data updates |

### 10.2 Known Issues (from GitHub)

| Issue | Symptom | Workaround |
|-------|---------|------------|
| #12239 | Axis titles misaligned when scale is reversed | Manually adjust title padding |
| #12234 | `chart.umd.js` should have readable types | Use ESM import for full type support |
| #12232 | Array of tuples not working when parsing disabled | Enable parsing or restructure data |
| #12177 | Chart no longer resizes correctly when enlarging window (v4.5.1) | Set explicit `resizeDelay` or call `chart.resize()` manually |
| #12143 | Changes to resize events and animation in 4.5.1 | Review migration notes for ResizeObserver changes |
| #10901 | `TypeError: Cannot read properties of null (reading 'getLabelAndValue')` | Ensure datasets are not mutated during tooltip evaluation |
| #10406 | PolarArea chart broken when all data values equal 0 | Add a minimum value guard in data preprocessing |
| #10059 | Inconsistent Doughnut arc spacing | Adjust `borderWidth` and `spacing` options |
| #9965 | Using TypeScript type definitions without modules | Use `chart.js/auto` with `<script>` and a global types reference |

### 10.3 Common Pitfalls

**Canvas reuse without destroy:**
```
Error: Canvas is already in use. Chart with ID '0' must be destroyed before...
```
**Prevention:** Always call `chart.destroy()` before creating a new chart on the same canvas element.

**Missing scale registration:**
Chart renders empty with no visible errors.
**Prevention:** When using tree-shaken imports from `chart.js`, ensure you register all required scale types (`CategoryScale`, `LinearScale`, etc.).

**Time scale without adapter:**
Throws `"This method is not implemented"` on first render.
**Prevention:** Install and import a date adapter before creating a time-based chart.

**Decimation not activating:**
Data points still render at full density despite enabling the plugin.
**Prevention:** Verify all three prerequisites: (1) line chart, (2) linear or time x-axis, (3) `parsing: false`.

> [!CAUTION]
> When updating chart data in-place (e.g., `chart.data.datasets[0].data.push(newPoint)`), you **must** call `chart.update()` afterward. Chart.js does not observe array mutations. Failing to call `update()` leaves the visual state stale, and can cause tooltip/interaction errors when internal metadata is out of sync with the data.

---

_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._
