Enabling Apache ECharts in React for Data Visualization
Making dashboards with charts and graphs is a pretty common part of the front-end developer experience, as well as deciding which JavaScript library to use for data visualization. As you explore all the different options - Recharts, Visx, D3 - you'll see they all do something you want, and lack something you need.
For a while I was using Nivo, a really impressive React component library, but it didn't have some features I needed like drilling down from one graph type into another, so I started looking into Apache ECharts.
This library is open-source, awesome, and incredibly easy to use. However, as of writing, the React component wrapper (echarts-for-react) that someone made is pretty out of date - last updated more than three years ago, and relies on an old version of ECharts that didn't have all the features I was looking for, so I made a tiny little wrapper that can just be imported as a component in a single file..
Demo
ECharts works by creating an option
object that holds all the configuration about the graph. That makes it incredibly easy to work with, because it doesn't require separate components for each graph type.
import { EChart } from './EChart'
export const App = () => {
const option = {
// ... chart configuration goes here
}
return <EChart option={option} />
}
Here's a little demo importing the default Line, Bar and Pie type charts.
Installation
Just install the echarts dependency and you're good to go.
npm i echarts
Using ECharts
With just a few methods from the API, you can get this up and running.
ECharts
First you'll need to create a div and use that ref to inject the ECharts instance, then you can initialize the echartInstance
with init()
. For as long as it exists, you can access the instanced with getInstanceByDom()
. When you're done, you can disconnect()
from the instance.
Function | Description |
---|---|
init |
Creates an ECharts instance |
getInstanceByDom |
Returns chart instance of dom container |
disconnect |
Disconnects interaction of multiple chart series. |
EChart Instance
Once you have an echartsInstance
in the DOM, you can use setOption
to update the configuration. We'll be able to update this with useEffect
in React. When you're done, you can destroy the instance with dispose()
Function | Description |
---|---|
setOption |
Configuration item, data, universal interface, all parameters and data can all be modified through setOption |
dispose |
Destroys chart instance, after which the instance cannot be used any more |
<EChart>
Component
To set up the basic Echart
component, you can create a ref
on an element and initialize the chart on mount using init()
. All the configuration of echarts is set in setOption
, so whatever option
you pass into the component will initialize the chart.
Everything you need to get set up can be handled in these two useEffect
s.
import React, { useMemo, useRef, useEffect } from 'react'
import { init, getInstanceByDom } from 'echarts'
export const EChart = ({
option,
chartSettings,
optionSettings,
style = { width: '100%', height: '350px' },
...props
}) => {
const chartRef = useRef(null)
useEffect(() => {
// Initialize chart
const chart = init(chartRef.current, null, chartSettings)
return () => {
chart?.dispose()
}
}, [])
useEffect(() => {
// Re-render chart when option changes
const chart = getInstanceByDom(chartRef.current)
chart.setOption(option, optionSettings)
}, [option])
return <div ref={chartRef} style={style} {...props} />
}
Now you'll have an element that contains the chart and re-renders if the options change.
Events
The component is pretty basic right now, and there's a few things you'll probably want to add. You can create an event handler to pass a click event into the chart like so:
<EChart
option={option}
events={{
click: () => {
console.log('Click handler!')
},
}}
/>
This enables any type of event and handler you want to add.
import React, { useMemo, useRef, useEffect } from 'react'
import { init, getInstanceByDom } from 'echarts'
export const EChart = ({
option,
chartSettings
optionSettings
style = { width: '100%', height: '350px' },
events = {}, ...props
}) => {
const chartRef = useRef(null)
useEffect(() => {
// Initialize chart
const chart = init(chartRef.current, null, chartSettings)
// Set up event listeners for (const [key, handler] of Object.entries(events)) { chart.on(key, (param) => { handler(param) }) }
// Return cleanup function
return () => {
chart?.dispose()
}
}, [])
useEffect(() => {
// Re-render chart when option changes
const chart = getInstanceByDom(chartRef.current)
chart.setOption(option, optionSettings)
}, [option])
return <div ref={chartRef} style={style} {...props} />
}
Resizing
You also might want the chart to resize if the screen changes, or if the layout changes, such as opening/closing a sidebar.
I created a debounced event with Lodash debounce()
and useMemo()
, which will be debounced 50
milliseconds. This will help performance so the chart doesn't constant run resize()
during a resize event, but attempts to do it only once.
const resizeChart = useMemo(
() =>
debounce(() => {
if (chartRef.current) {
const chart = getInstanceByDom(chartRef.current)
chart.resize()
}
}, 50),
[]
)
I used the ResizeObserver()
WebAPI to determine when to resize in the useEffect
.
const resizeObserver = new ResizeObserver(() => {
resizeChart()
})
resizeObserver.observe(chartRef.current)
Now the EChart
component is complete - it will resize on window or layout change, pass events through, and update the graph based on the option
configuration passed in.
import React, { useMemo, useRef, useEffect } from 'react'
import { init, getInstanceByDom } from 'echarts'
import { debounce } from 'lodash'
export const EChart = ({
option,
chartSettings,
optionSettings,
style = { width: '100%', height: '350px' },
events = {},
...props
}) => {
const chartRef = useRef(null)
// Debounce resize event so it only fires periodically instead of constantly const resizeChart = useMemo( () => debounce(() => { if (chartRef.current) { const chart = getInstanceByDom(chartRef.current) chart.resize() } }, 50), [] )
useEffect(() => {
// Initialize chart
const chart = init(chartRef.current, null, chartSettings)
// Set up event listeners
for (const [key, handler] of Object.entries(events)) {
chart.on(key, (param) => {
handler(param)
})
}
// Resize event listener const resizeObserver = new ResizeObserver(() => { resizeChart() }) resizeObserver.observe(chartRef.current)
// Return cleanup function
return () => {
chart?.dispose()
if (chartRef.current) { resizeObserver.unobserve(chartRef.current) } resizeObserver.disconnect() }
}, [])
useEffect(() => {
// Re-render chart when option changes
const chart = getInstanceByDom(chartRef.current)
chart.setOption(option, optionSettings)
}, [option])
return <div ref={chartRef} style={style} {...props} />
}
Conclusion
Setting up the EChart
component was a good exercise in creating my own simple wrapper for a JavaScript library without relying on the third-party React implementation. Supposedly, other frameworks like Svelte make it a lot easier to implement third party JavaScript libraries without making them "React-specific, but I work primarily in React, so it's a useful skill to have.
You can view the Demo and source code on CodeSandbox, which uses the completed EChart
component to display a line, bar, and pie chart.
Comments