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(...)orecharts.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.
// 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.
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 firstTry it yourself
- 1
Change the visx tick count
In the first example change
numTicks={6}tonumTicks={12}. The x ticks line gains a label for every month:2025, February, March, Apriland so on toDecember. - 2
Fix the Chart.js error
In the second example give
ChartNoCleanupa cleanup:useEffect(() => { const c = new Chart(...); return () => c.destroy(); }, []). TheUncaught Error: Canvas is already in uselines disappear. - 3
Swap a Plot mark
In the third example change
Plot.lineY(rows, ...)toPlot.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 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
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,000Two 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
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: canvasThe 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
Console
Nothing logged yet.Expected console output
svg elements: 1 | div children: 1Break 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.
Break #2
Create an ECharts instance twice on one element
Look at EchartsNoCleanup: it calls echarts.init and never disposes.
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/autoregisters everything and is easy, while importingChartand registering onlyBarElement,CategoryScale,LinearScaleand what you use keeps the bundle smaller. The same goes for ECharts: importecharts/coreand 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
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
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, andLinePathmakes the same path text asd3.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
Chart.js (4.5.1) draws on a
<canvas>from one config object:type,data,options. It is not React-aware. The official wrapperreact-chartjs-2renders 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, andchart.destroy()in the cleanup. - 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 callchart.resize(). The React wrapperecharts-for-reactdoes the init and dispose for you, but is also not in this runtime, so the live example does it by hand withecharts.initanddispose. - 5
Observable Plot (0.6.17) is a grammar:
Plot.lineY(rows, { x: "date", y: "rides" })is one mark, andPlot.plot({ marks: [...] })returns a ready-made<svg>element. It has no React API: you render adivand 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
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
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
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
Name the three questions you would ask to choose between D3 + JSX, Recharts, ECharts and Plot for a new chart.
Why does a Chart.js chart created in useEffect throw under StrictMode, and what is the fix?
What does it mean that visx scales are "D3's scales behind a config object"?
What do npm download counts tell you, and what do they not?
Practice
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.
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.