Skip to content

Efficient Data Visualization with React

How to handle amCharts Charts efficiently

amCharts is a JavaScript library for data visualization with built-in support for TypeScript and ES6 modules. It is also fully compatible with Angular, React, Vue.js. amCharts offers a wide selection of types of charts, as presented here.

I’ve been using amCharts for months and now I know its major strengths and downsides. When used for static charts, it’s simply amazing. What about dynamic charts?

Initializing an amCharts chart takes a lot of time and resources. Imagine doing this every time we need to change the data visualized, i.e. as a result of an event. This can easily become the bottleneck of a web application, especially if there are many charts (depending on this event) on the same page. This is where memoization comes into play!

Please note that the main goal of this article is not to show how either how amCharts works, or what memoization is. This is why I strongly recommend reading this and this first.

Let’s build an efficient React component designed to wrap any type of amCharts chart.

Building The Chart Component

Our generic component can be defined as follows:

function Chart(props) {
    const { idHtmlElement, onInitialize, ...remainingProps } = props;

    const [chart, setChart] = useState(undefined);

    const initializeChart = React.useMemo(() => {
            return () => {
                const chart = am4core.create(idHtmlElement, am4charts.XYChart);

                // ... customizing the chart

                return chart;
            }
        }, []
    )

    useEffect(() => {
        const chart = initializeChart();
        
        // saving the chart reference in the component's state
        // since it might be useful
        setChart(chart);
        
        // passing the chart reference to the parent component
        onInitialize(chart);        
    }, []);

    return (
        <div
            id={idHtmlElement}
            {...remainingProps}
        />
    )
}

export default Chart;

The am4core.create function returns the chart instance, which is what allows us to manipulate the behavior of the chart itself. The creation of the chart, i.e. its initialization, should be executed only the first time the component is rendered. Otherwise, this component would become extremely inefficient. This is why we wrapped our initialization function in a useMemo callback. This way, we have memoized the function returning the char instance, which is called only when the component is invoked for the first time. Every time the component re-renders the chart cached value will be used, avoiding the initialization overhead.

If we want our chart to be manipulated without being created anew every time, we need to expose its reference to the parent component. For this reason, we added the onInitialization prop. Thanks to it, the parent can change the appearance, data, and behavior of the chart operating directly on its instance.

The main strengths of this approach are two:

  • Avoiding code duplication by creating a component per chart type
  • Allowing the parent component to interact directly with the chart whatever and whenever it wants

ChartComponent In Action

Let’s say we want to create a temporal evolution line chart, just like this one:

A temporal evolution line chart made with amCharts
A temporal evolution line chart made with amCharts

The data to be visualized will be retrieved from an AJAX call, whose result depends on the time span selected by the user. First of all, let’s define the TemporalEvolutionChart component:

function TemporalEvolutionChart(props) {
    const { idHtmlElement, onInitialize, ...remainingProps } = props;

    const [chart, setChart] = useState(undefined);

    const initializeChart = React.useMemo(() => {
            return () => {
                const chart = am4core.create(idHtmlElement, am4charts.XYChart);

                // defining the temporal evolution line chart
                
                const categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
                categoryAxis.dataFields.category = "time";
                
                const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

                const lineSeries = chart.series.push(new am4charts.LineSeries());
                lineSeries.dataFields.valueY = "value";
                lineSeries.dataFields.categoryX = "time";

                chart.scrollbarX = new am4core.Scrollbar();
                chart.scrollbarX.parent = chart.bottomAxesContainer;

                chart.legend = new am4charts.Legend();

                return chart;
            }
        }, []
    )
    
    useEffect(() => {
        const chart = initializeChart();

        // saving the chart reference in the component's state
        // since it might be useful
        setChart(chart);

        // passing the chart reference to the parent component
        onInitialize(chart);
    }, []);

    return (
        <div
            id={idHtmlElement}
            {...remainingProps}
        />
    )
}

export default TemporalEvolutionChart;

The parent component is in charge of making the call to retrieve the data to be shown in the chart and pass it to it. This is done by assigning the result of the AJAX call to the data property of the chart instance. amCharts chart will automatically render the new data.

Et voilà! As soon as a user changes the time span of interest, an AJAX call is made (thanks to the API definition layer introduced here), and the chart is updated accordingly. This, in a very efficient way without initializing the chart every time.

Conclusion

In this article, we showed an efficient way to use amCharts with React. Initializing an amCharts chart is a time-consuming operation, which should be executed only when absolutely necessary. This approach is a great example of how to save resources and prevent users from feeling frustrated due to a very slow web page. Thanks for reading! I hope that you found this article helpful.

nv-author-image

Antonello Zanini

I'm a software engineer, but I prefer to call myself a Technology Bishop. Spreading knowledge through writing is my mission.View Author posts

Want technical content like this in your blog?