chartjs-plugin-annotation
Draws lines, boxes, points, labels, polygons, and ellipses on Chart.js charts, with full interactivity.
Repository Health
Technical Analysis
chartjs-plugin-annotation is the official annotation plugin for Chart.js 4.x, letting you draw lines, boxes, points, labels, polygons, and ellipses directly on the chart area to call out thresholds, ranges, or events in your data. It works across line, bar, scatter, and bubble charts on linear, logarithmic, time, or category scales, and includes a doughnut-label annotation type for placing text, images, or custom canvas content in the center of doughnut charts.
Annotations are fully scriptable and support click, hover (enter/leave), and drag interaction hooks, plus configurable animations for position and color changes. Because it registers as a standard Chart.js plugin rather than an inline plugin, it can be explicitly registered, unregistered, and reused across multiple chart instances with predictable lifecycle behavior tied into Chart.js’s own update cycle (beforeUpdate, afterDraw, afterDestroy, etc.).
Maintained under the official chartjs GitHub organization, it ships with full TypeScript type definitions, an extensive Karma/Jasmine test suite covering every annotation type, and versioned releases that track specific Chart.js compatibility ranges (v3.x for Chart.js 4.x, v2.x for Chart.js 3.7-3.9, v1.x for Chart.js 3.0-3.6, v0.5.x for Chart.js 2.x).
What You Get
- Six annotation types — line, box, point, label, polygon, and ellipse — plus a doughnutLabel type for center-of-doughnut content
- Scriptable options so annotation position, color, and visibility can be computed per-render from chart context
- Built-in interaction hooks for click, enter, and leave events on individual annotations
- Configurable per-property animations (numbers and colors) driven by Chart.js’s own animation system
- Full TypeScript type definitions for all annotation types and plugin options
- Explicit register/unregister lifecycle so the plugin can be scoped to specific charts rather than applied globally
Common Use Cases
- Marking a threshold or target value with a horizontal or vertical line annotation on a time-series chart
- Highlighting a date range or value band with a semi-transparent box annotation
- Adding clickable label annotations that open detail views when a user interacts with a specific data point
- Placing summary text or an icon in the center of a doughnut chart using the doughnutLabel type
- Drawing a polygon or ellipse to circle an outlier cluster of points on a scatter chart
Under The Hood
Architecture
The plugin registers itself with Chart.js via Chart.register(Annotation) (src/index.js) and implements the full Chart.js plugin lifecycle in src/annotation.js: beforeInit seeds a per-chart state object in a Map keyed by chart instance, beforeUpdate normalizes the annotations option (accepting either an object keyed by id or a plain array) into a working list, afterDataLimits expands scale ranges to fit annotations via adjustScaleRange, and afterUpdate drives updateListeners, updateElements, and updateHooks each render. Drawing is dispatched through a shared draw() function invoked from beforeDatasetsDraw/afterDatasetsDraw/beforeDraw/afterDraw hooks based on each annotation’s configured drawTime. Per-type behavior (line, box, point, label, polygon, ellipse, doughnutLabel) lives in src/types/*.js, each implementing a shared resolveElementProperties/draw contract that elements.js and annotation.js consume generically, while events.js and interaction.js hit-test the currently visible elements against pointer events to dispatch click/enter/leave callbacks. The state-per-chart-instance design lets the plugin be registered once but safely support many independent chart instances.
Tech Stack
Written in plain ES modules ("type": "module") with a chart.js >=4.0.0 peer dependency and no other runtime dependencies, relying on chart.js/helpers for shared utilities. TypeScript consumers get hand-written .d.ts declarations under types/, type-checked via a dedicated tsconfig in types/tests/. Builds run through Rollup (rollup.config.js plus @rollup/plugin-json, @rollup/plugin-node-resolve, @rollup/plugin-terser) producing CJS, ESM, and minified browser bundles. Tests run in real browsers via Karma + Jasmine (with Chrome/Firefox launchers and a Rollup preprocessor for bundling specs), supplemented by a separate Mocha-driven integration suite and pixelmatch for pixel-level comparisons. Documentation is built with VuePress plus TypeDoc-extracted API references.
Code Quality
The test/ directory contains 246 files including dedicated spec files per annotation type (line, box, point, label, polygon, ellipse, doughnutLabel) plus animation, events, hooks, and helpers specs — indicating deliberate per-feature coverage rather than a handful of smoke tests. A separate test/integration/ suite runs under Mocha with full stack traces enabled. ESLint extends the shared eslint-config-chartjs style guide plus plugin:es/no-new-in-es2019 and markdown-embedded-code linting, and the TypeScript types are both lint-checked and type-checked independently. GitHub Actions CI runs the full suite on every push, with separate workflows for npm publishing, release drafting, documentation builds, and bundle-size tracking. JSDoc typedef comments annotate function signatures throughout the plain-JS source, giving IDE-level type hints even outside the .d.ts files.
API Design
The public surface is entirely declarative: annotations are plain objects (or an array of them) under options.plugins.annotation.annotations, requiring only a one-time Chart.register() call with no imperative setup. Any option can be scriptable — a function resolved against chart/element context at render time — letting consumers compute annotation position, color, or visibility dynamically without touching Chart.js’s lower-level element APIs. Interaction callbacks (click, enter, leave) are defined inline on each annotation object rather than requiring manual canvas event-listener wiring. This closely mirrors Chart.js’s own configuration idioms — consistent and low-boilerplate for anyone already familiar with Chart.js options, though the per-type option surface (seven distinct annotation shapes) takes some reference-doc lookups to master, and annotation changes still require an explicit chart.update() call, matching Chart.js’s broader reactivity model rather than offering something more automatic.
Used by 7 apps in this directory
Apache Airflow
Data Engineering
Define, schedule, and monitor complex data workflows as Python code — with a powerful UI, 80+ provider integrations, and battle-tested scalability across thousands of production deployments.
Argo Workflows
Devops · Data Engineering
The most popular Kubernetes-native workflow engine for orchestrating containerized DAGs, ML pipelines, CI/CD, and parallel batch jobs at scale.
Gatus
Monitoring · Devops
Developer-oriented health dashboard with active endpoint probing, multi-protocol checks, and 40+ alerting integrations so you know about failures before your users do.
Ghostfolio
Invoicing Finance
Track your stocks, ETFs, and crypto with a privacy-first, self-hostable wealth management platform built for data-driven investors.
PostHog
Analytics · Monitoring · Developer Tools
The all-in-one open source product platform combining analytics, session replay, feature flags, error tracking, AI observability, and a built-in data warehouse in a single self-hostable stack.
SigNoz
Monitoring · Analytics
Self-host your entire observability stack — logs, metrics, traces, and LLM monitoring — in one OpenTelemetry-native platform, without the Datadog bill.
Unleash
Developer Tools · Devops · Ab Testing Experimentation
The open-source feature management platform that lets you ship code to production and control who sees it — without redeploying.