Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Chart.js Administrator & Developer 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 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:

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:

{
  "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:

SOURCE_DATE_EPOCH=$(date +%s) pnpm build

4.4 CDN Deployment

The UMD bundle is designed for CDN delivery:

<!-- 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:
import {
  Chart,
  LineController,
  LineElement,
  PointElement,
  LinearScale,
  CategoryScale,
  Tooltip,
  Legend
} from 'chart.js';

Chart.register(
  LineController,
  LineElement,
  PointElement,
  LinearScale,
  CategoryScale,
  Tooltip,
  Legend
);
  1. 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:

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)
// 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:

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:

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:

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:

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

Vue: Use onMounted/onBeforeUnmount:

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

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

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.