Command Palette

Search for a command to run...

Hectal
PHASE 14Advanced ~26 min· topic 5 of 7

Topic 14.5

visx, Chart.js, ECharts and Observable Plot

In one line

Four more ways to draw a chart in a React app: visx (D3 maths in small React parts), Chart.js and ECharts (canvas libraries driven by a config object), and Observable Plot (a short grammar for fast exploration). Each has a cost, and a table helps you choose.

Think of it like this

Buying a kitchen. You can buy a ready-made fitted kitchen (Recharts, ECharts), buy cupboards and worktops to assemble yourself (visx), hire a joiner to build each piece (D3 alone), or sketch the layout on paper first (Plot). The right choice depends on how special your kitchen is and how long you have.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

config object
One JavaScript object that describes the whole chart (type, data, options). The library reads it and draws.
wrapper
A small React package that renders the element for a non-React library and keeps it in step with props.
canvas library
A chart library that draws pixels on a <canvas>, so marks are not DOM elements.
grammar of graphics
A way to describe a chart as marks (line, dot, bar) bound to data fields, instead of drawing shapes.
instance
The live chart object a library returns, such as new Chart(...) or echarts.init(...). It must be destroyed.
download count
How many times npm served the package in a period. Installs inside other packages count too.

Step by step

01visx: the same scales and path, as components

The first example draws the whole year with scaleUtc, scaleLinear, LinePath, AxisBottom and AxisLeft. It also draws the same line with d3.line and compares the two path strings.

02Chart.js and ECharts: a ref, an effect and a cleanup

The second example mounts both libraries in StrictMode. A Chart.js chart created twice on one canvas throws. An ECharts instance created twice warns. The correct versions destroy or dispose in the cleanup.

Here is the official wrapper code for both, as you would write it in a real project. It cannot run here because the packages are not in this page's runtime.

with the official wrappers (not runnable here)whole filejsx
// npm install chart.js react-chartjs-2 echarts echarts-for-react
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Tooltip } from "chart.js";
import { Bar } from "react-chartjs-2";
import ReactECharts from "echarts-for-react";

ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip); // import only what you use

<Bar data={{ labels: months, datasets: [{ label: "Rides", data: totals }] }} options={{ animation: false }} />
<ReactECharts option={option} style={{ height: 220 }} />

03Plot: one line of marks, an element in a div

The third example renders Plot.plot(...) into a div from an effect and removes it in the cleanup. React owns the div, Plot owns what is inside.

04Choose with a table

The table below puts the research's numbers next to what each library asks of you. Use it as a starting point, then draw your own chart in your own data with two of them before you decide.

choosing table (npm downloads, 13 Sep to 12 Oct 2026)whole filetext
Library        Downloads/month  Draws on      You write                      Best when
recharts       263,284,480      SVG           JSX parts, small config        standard charts in React, fast
d3             98,405,577       you choose    everything                     custom visuals, full control
chart.js       57,653,187       canvas        one config object              simple charts, many users know it
echarts        25,299,143       canvas/SVG    one big option object          many chart types, big data
@visx/scale    27,259,562       SVG           D3 maths + parts               custom charts inside React
observable plot 3,249,247       SVG           marks in one call              exploring data, notebooks, drafts
highcharts     11,732,361       SVG           config object                  read its licence terms first

Try it yourself

  1. 1

    Change the visx tick count

    In the first example change numTicks={6} to numTicks={12}. The x ticks line gains a label for every month: 2025, February, March, April and so on to December.

  2. 2

    Fix the Chart.js error

    In the second example give ChartNoCleanup a cleanup: useEffect(() => { const c = new Chart(...); return () => c.destroy(); }, []). The Uncaught Error: Canvas is already in use lines disappear.

  3. 3

    Swap a Plot mark

    In the third example change Plot.lineY(rows, ...) to Plot.dot(rows, { x: "date", y: "rides", r: 1.5 }). The same data is now 365 dots instead of a line: one word of the grammar changed the chart.

Code & diagrams

visx: D3 scales and shapes as React parts New tab

visx draws the same string D3 would. The x labels start with `2025` because the first tick, 1 January, is a year boundary in D3's time ticks (Topic 6.3). visx's `scaleUtc` is used, not `scaleTime`: local time would shift days for people far from Ardenvale.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

path starts: M0,133.75L1.374,117.936L2.747,
same path text as d3.line: true
x ticks: 2025, April, July, October
y ticks: 0, 1,000, 2,000, 3,000, 4,000
Chart.js and ECharts in StrictMode: without and with cleanup New tab

Two charts are broken on purpose: the console shows what StrictMode finds. Chart.js throws, and React logs which component failed. ECharts only warns, and then works with two instances on one box.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Uncaught Error: Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID '' can be reused.
Uncaught Uncaught Error: Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID '' can be reused.
An error occurred in the <ChartNoCleanup> component.

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.

[ECharts] There is a chart instance already initialized on the dom.
Chart.js good chart drawn: true
ECharts good chart alive: true | renderer: canvas
Observable Plot inside a React div New tab

The chart is one line of marks plus a rule at zero. Plot chose the axes, ticks and gridlines. Keep `options` stable (defined once, or in `useMemo`), or the effect runs every render.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

svg elements: 1 | div children: 1

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Create a Chart.js chart twice on one canvas

Look at ChartNoCleanup in the second example: its effect returns nothing.

terminal
$ Console
── what you'll see ──
Uncaught Error: Canvas is already in use. Chart with ID '0' must be destroyed before the canvas with ID '' can be reused.
An error occurred in the <ChartNoCleanup> component.

Break #2

Create an ECharts instance twice on one element

Look at EchartsNoCleanup: it calls echarts.init and never disposes.

terminal
$ Console
── what you'll see ──
[ECharts] There is a chart instance already initialized on the dom.
A warning, not an error: the page keeps working, with a leaked instance.

Myth vs fact

Myth

The most downloaded library is the best one for my project.

Fact

Download counts include installs pulled in by other packages and by build tools. They show how widely a package is installed, not how well it fits your chart.

Myth

Canvas libraries are always faster than SVG ones.

Fact

Canvas wins at thousands of marks. With a hundred marks, SVG draws just as fast and gives you elements to style, focus and test. Canvas also needs extra work for hover and for screen readers.

Myth

A wrapper means I do not need to understand cleanup.

Fact

Wrappers do the init and destroy for you. They do not fix passing a new options object on every render, which makes them re-create or re-apply the chart every time.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Chart.js 4 is tree-shakeable: chart.js/auto registers everything and is easy, while importing Chart and registering only BarElement, CategoryScale, LinearScale and what you use keeps the bundle smaller. The same goes for ECharts: import echarts/core and the chart types, renderers and components you need, not the whole package.

  • ▸

    ECharts setOption(option, true) replaces the old option, while the default merges. Merging is useful for updating one series, and a surprise when you remove a series from your data and it stays on screen.

  • ▸

    visx has no state: it is scales, shapes and helpers. Tooltips (@visx/tooltip) give you positioning and portal helpers, and you write the hover logic with the nearest-point methods of Topic 8.3.

  • ▸

    Observable Plot returns a real DOM element with its own <title>/aria support for marks you choose (tip: true), and can be rendered on the server with a DOM implementation. It is not designed for animation or for charts that must match a brand layout to the pixel.

Remember this

  1. 1

    The problem this solves: Topics 14.1 and 14.4 are two ends of one line. Between them and beyond them sit other libraries, each with its own way of working, and a team has to pick one. The choice is mostly about three things: who owns the DOM, SVG or canvas, and how much you can change.

  2. 2

    visx (from Airbnb) is "D3 for the maths, React for the DOM" as small packages: @visx/scale, @visx/shape, @visx/axis, @visx/group, @visx/tooltip. Its scales are the D3 scales behind a config object, and LinePath makes the same path text as d3.line. You assemble the chart like Topic 14.1, but axes and shapes are components, so no refs are needed. This phase uses version 4.0.0 of each package, which needs React 18 or 19.

  3. 3

    Chart.js (4.5.1) draws on a <canvas> from one config object: type, data, options. It is not React-aware. The official wrapper react-chartjs-2 renders the canvas and keeps the chart in step with props; it is not available in this page's runtime, so the live example does the same job by hand: a ref, an effect, and chart.destroy() in the cleanup.

  4. 4

    ECharts (6.1.0, Apache) also takes one option object and draws on canvas (or SVG with renderer: "svg"). It has many chart types, large-data features, and tooltips built in. It does not watch its box: you call chart.resize(). The React wrapper echarts-for-react does the init and dispose for you, but is also not in this runtime, so the live example does it by hand with echarts.init and dispose.

  5. 5

    Observable Plot (0.6.17) is a grammar: Plot.lineY(rows, { x: "date", y: "rides" }) is one mark, and Plot.plot({ marks: [...] }) returns a ready-made <svg> element. It has no React API: you render a div and put the element into it from an effect. It is excellent for exploring data quickly and less suited to dashboards that must match an exact design.

  6. 6

    Highcharts is a mature, widely used commercial charting library with strong accessibility features. Its licence is not a free open-source one for every use, and this course has not checked its terms, so read them on the vendor's site before using it in a product.

  7. 7

    Cleanup differs by library. Chart.js says Canvas is already in use. Chart with ID '0' must be destroyed before the canvas ... can be reused. if StrictMode's second run creates a chart on the same canvas. ECharts logs a warning: There is a chart instance already initialized on the dom. Plot's element is replaced each time, so it leaks nothing. In all three, the effect must clean up what it created.

  8. 8

    Choosing. The numbers below are npm download counts for the last month of the research (13 September to 12 October 2026). They include installs pulled in by other packages, so they show how widely a package is installed, not how many people chose it directly.

Explain it without notes

01

Name the three questions you would ask to choose between D3 + JSX, Recharts, ECharts and Plot for a new chart.

02

Why does a Chart.js chart created in useEffect throw under StrictMode, and what is the fix?

03

What does it mean that visx scales are "D3's scales behind a config object"?

04

What do npm download counts tell you, and what do they not?

Practice

01

Write a ChartJsBar component with useRef and useEffect that takes labels and values, destroys the chart in cleanup, and updates the existing chart when the props change instead of creating a new one.

02

A teammate passes option={{ series: [...] }} inline to an ECharts wrapper and the chart flickers on every hover. Explain and fix it.

Trade-offs

  • ↔

    Config-object libraries give the most chart types for the least code, and lock you into their look and their update model. Component and parts libraries cost more code and let you change any detail.

  • ↔

    Every library you add costs bundle size and a second set of rules to learn: choose one main chart library per app and keep the others for the places they are clearly better.

Done when you can

  • I can draw a line with visx using D3 scales and say why the path matches d3.line.

  • I can mount Chart.js and ECharts from an effect with a correct cleanup.

  • I can render an Observable Plot figure into a React div.

  • I can choose a library from the questions: standard or custom, how many marks, how much control.

  • I can say what download counts do and do not show, and why I must read the Highcharts licence.