Article
Synchronizing multiple charts within a React component rendering list
Enhancing visualization of data by synchronizing the interaction between multiple charts created with Apache ECharts in ReactIn today's tech landscape, React, Angular, and Vue stand out as the most widely recognized front-end development libraries or frameworks. When developing user interfaces, developers choose these libraries or frameworks based on specific project requirements. Similarly, when designing dashboards, developers seek out visualization libraries to help streamline development efforts.
When developing dashboard interfaces in particular, seamless interaction synchronization, such as sharing mouse events and zoom functionality across multiple charts, is essential. Finding visualization libraries for these dashboard interfaces is quite challenging, especially when they must integrate well within other UI libraries or frameworks.
In this article, I describe this complex scenario that synchronizes multiple instances of ECharts line charts that are encapsulated with individual React components within a rendering list.
Technology stack I used
You'll need the following technology stack:
- react v18.2.0
- react-Bootstrap v2.10.2
- bootstrap v5.3.3
- echarts-for-react v3.0.2
- Apache ECharts v5.5.0
We use the Apache ECharts sample data, Rainfall vs Evaporation, which includes time series data for rainfall and evaporation for 3 different regions (Region A, Region B and Region C).
My implementation
The typical main steps for implementing this scenario are as follows:
- Create one React component
RainfallEvaporationLineChartto wrap the line chart of ECharts. - Create one rendering list of the React component
RainfallEvaporationLineChartfor the 3 different regions. - Synchronize the charts in the React component rendering list.
Implementing steps 1 and 2 is relatively straightforward. However, step 3 presents numerous challenges and issues, as outlined in the next section.
Challenges encountered
- Apache ECharts provides one
connectmethod that connects the interaction of multiple chart series, but you need to know the ECharts instance first. - The eharts-for-react library provides one
getEchartsInstance()API to get the ECharts instance object, but you need to know the React referencing values ofReactEChartscomponent usingrefs. The API works very well when therefsare predefined; however, in our scenario, predefiningrefsis not feasible. React Refs come with several limitations:
- You cannot call
useRefin a loop, in a condition, or inside amap()call due to Hooks must only be called at the top-level of React component. React offers two formal solutions to address the issue. First, you can create a single ref to the parent element and traverse down to the individual child nodes. Or, you can pass a callback function to the ref attribute, enabling the maintenance of a custom refs array or map. See How to manage a list of refs using a ref callback for more detailed information. - You cannot directly use a ref to access DOM nodes of another React component. Attempting to do so will result in the warning: "Function components cannot be given refs. Attempts to access this ref will fail."
React.forwardRef()is the recommended solution provided by React. See Accessing another components dom nodes for more detailed information. - The
ref.currentvalue may constantly change because "React sets ref.current during the commit. Before updating the DOM, React sets the affected ref.current values to null. After updating the DOM, React immediately sets them to the corresponding DOM nodes. Usually, you will access refs from event handlers." See When React attaches the refs for more detailed information.
- You cannot call
The primary challenge here lies in obtaining the ECharts instance object, which proves to be difficult using React refs and ensuring smooth functionality.
Implemented solution
Upon examining the component properties of the echarts-for-react library, you can see the onChartReady property, which is a callback function that receives the ECharts object as its parameter and executes when the chart is ready. So, you can use this callback to address the issue and implement the desired feature.
My code is in this IBM GitHub repository
Let's look at the main functions in the App.tsx file:
...
function App() {
// Set sample data
...
// Set group id to connect the created charts
const groupId = 'rainfall-evaporation-line-chart-group-id'
// Set Echarts instannce
const echartsInstanceStore = {} as Record<string, EChartsInstance>;
// Callback function for echarts onChartReady event
function onChartReadyCallback(instance: EChartsInstance) {
// Set group id and add new echarts instance to the store
if (instance) {
if (!echartsInstanceStore[instance.id]) {
instance.group = groupId;
echartsInstanceStore[instance.id] = instance;
}
}
// Remove echarts instance when it is null
for (const [key, value] of Object.entries(echartsInstanceStore)) {
if (!value) {
delete echartsInstanceStore[key];
}
}
// Enable interaction synchronization for multiple charts
if (Object.entries(echartsInstanceStore).length > 1) {
echarts.disconnect(groupId);
echarts.connect(groupId);
}
}
// Create the rendering list for charts
const charts = regionData.map((region, i) => {
return <Col sm={12} md={12} lg={6} xl={6} xxl={6} key={i}>
<Card style={{ margin: '16px', padding: '8px'}}>
<Card.Body>
<Card.Title>Rainfall vs Evaportation in {region}</Card.Title>
<RainfallEvaporationLineChart
data={{'time': timeData,
'rainfall': rainfallData[i],
'evaporation': evaporationData[i]}}
onChartReady={onChartReadyCallback}>
</RainfallEvaporationLineChart>
</Card.Body>
</Card>
</Col>;
}
);
return (
<Container>
<Row>
{charts}
</Row>
</Container>
);
}
Results and outcomes
The three charts for the three regions look as follows:

The following animation illustrates the final results and outcomes:

Summary and next steps
This article demonstrates how to synchronize multiple ECharts charts encapsulated with individual React components within a rendering list in a simple way.
You can learn more about React and Echarts from these guides: