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 React

By

Wusong Fang

In 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:

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:

  1. Create one React component RainfallEvaporationLineChart to wrap the line chart of ECharts.
  2. Create one rendering list of the React component RainfallEvaporationLineChart for the 3 different regions.
  3. 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

  1. Apache ECharts provides one connect method that connects the interaction of multiple chart series, but you need to know the ECharts instance first.
  2. The eharts-for-react library provides one getEchartsInstance() API to get the ECharts instance object, but you need to know the React referencing values of ReactECharts component using refs. The API works very well when the refs are predefined; however, in our scenario, predefining refs is not feasible.
  3. React Refs come with several limitations:

    • You cannot call useRef in a loop, in a condition, or inside a map() 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.current value 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.

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:

Three charts for the three regions in our data

The following animation illustrates the final results and outcomes:

Animated GIF of the three charts

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: