TL;DR: Synchronized charts link multiple charts for coordinated data analysis. Syncfusion React Charts allows for simultaneous interactions like tooltips, crosshairs, zooming, and selection across charts. This enhances real-time analysis and comparison, making data insights more accessible. This blog details how to implement these features in React Charts.
Synchronized charts refer to a visualization technique where multiple charts or graphs are linked together to display data in a coordinated manner. This approach is beneficial when analyzing relationships and trends between different data sets.
A synchronized chart provides access and interaction with two or more charts simultaneously. This means that actions performed on one chart, such as positioning a cursor, clicking on a specific point, or activating a tooltip, are reflected simultaneously on corresponding positions or data points across other synchronized charts based on their x and y coordinates.
In this blog, we’ll explore the synchronization feature in the Syncfusion React Charts component. Here, we’ll use this feature to examine the relationship between dollar value changes for different currencies.
The use cases of synchronized charts are as follows:
Let’s seamlessly analyze market data (relationship between dollar value changes for different currencies) by configuring the following elements in the React Charts:
You can easily synchronize tooltips across multiple charts, enhancing the user experience of interconnected data. This process involves two essential methods: showTooltip and hideTooltip.
When a user hovers over a data point in a chart, the showTooltip method enables the presentation of relevant information across several charts. This feature makes it possible to display relevant data simultaneously in synchronized charts.
The following are the parameters of the showTooltip method:
The following code example shows how to synchronize tooltips across the charts.
import { ChartComponent, SeriesCollectionDirective, LineSeries, AreaSeries, Tooltip, DateTime, SeriesDirective, Inject, } from '@syncfusion/ej2-react-charts'; import { synchronizedData } from './financial-data'; import { Browser } from '@syncfusion/ej2-base'; export let zoomFactor; export let zoomPosition; export let pointColors = []; const Candle = () => { let chart1; let chart2; let chart1MouseLeave = (args) => { chart2.hideTooltip(); }; let chart1MouseMove = (args) => { if ((!Browser.isDevice && !chart1.isTouch && !chart1.isChartDrag) || chart1.startMove) { chart2.startMove = chart1.startMove; chart2.showTooltip(args.x, args.y); } }; let chart1MouseUp = (args) => { if (Browser.isDevice && chart1.startMove) { chart2.hideTooltip(); } }; let chart2MouseLeave = (args) => { chart1.hideTooltip(); }; let chart2MouseMove = (args) => { if ((!Browser.isDevice && !chart2.isTouch && !chart2.isChartDrag) || chart2.startMove) { chart1.startMove = chart2.startMove; chart1.showTooltip(args.x, args.y); } }; let chart2MouseUp = (args) => { if (Browser.isDevice && chart2.startMove) { chart1.hideTooltip(); } }; return <div className="grid"> <div className="g-col-6 g-col-md-4"> <ChartComponent id="container1" ref={chart => chart1 = chart} width="30%" chartMouseLeave={chart1MouseLeave.bind(this)} chartMouseMove={chart1MouseMove.bind(this)} chartMouseUp={chart1MouseUp.bind(this)} tooltip={{ enable: true, shared: true, header: '', format: '<b>€${point.y}</b><br>${point.x} 2023'}} title="US to Euro"> <Inject services={[LineSeries, DateTime, Tooltip]} /> <SeriesCollectionDirective> <SeriesDirective type="Line" dataSource={synchronizedData} xName="USD" yName="EUR"></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> <div className="g-col-6 g-col-md-4"> <ChartComponent id="container2" width="30%" ref={chart => chart2 = chart} chartMouseLeave={chart2MouseLeave.bind(this)} chartMouseMove={chart2MouseMove.bind(this)} chartMouseUp={chart2MouseUp.bind(this)} tooltip={{ enable: true, shared: true, header: '', format: '<b>₹${point.y}</b><br>${point.x} 2023' }} title="US to INR"> <Inject services={[AreaSeries, DateTime, Tooltip]} /> <SeriesCollectionDirective> <SeriesDirective type="Area" dataSource={synchronizedData} xName="USD" yName="INR"></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> </div> }; export default Candle; const root = createRoot(document.getElementById('sample')); root.render(<Candle />);
Refer to the following output image.
Synchronizing crosshairs across multiple charts can significantly enhance data analysis and comparison. The showCrosshair and hideCrosshair methods facilitate this synchronization.
When hovering over one chart, the showCrosshair method gets invoked to show the corresponding points in the other charts.
The showCrosshair method takes the following parameters:
The following code example shows how to synchronize crosshair across the charts.
import { createRoot } from 'react-dom/client'; import * as React from 'react'; import { Chart, AreaSeries, SplineSeries, DateTime, Crosshair, IMouseEventArgs, ChartComponent, SeriesCollectionDirective, SeriesDirective, Inject } from '@syncfusion/ej2-react-charts'; import { synchronizedData } from './financial-data'; import { Browser } from '@syncfusion/ej2-base'; export let zoomFactor; export let zoomPosition; export let pointColors = []; const Candle = () => { let chart1; let chart2; let chart1MouseLeave = (args) => { chart2.hideCrosshair(); }; let chart1MouseMove = (args) => { if ((!Browser.isDevice && !chart1.isTouch && !chart1.isChartDrag) || chart1.startMove) { chart2.startMove = chart1.startMove; chart2.showCrosshair(args.x, args.y); } }; let chart1MouseUp = (args) => { if (Browser.isDevice && chart1.startMove) { chart2.hideCrosshair(); } }; let chart2MouseLeave = (args) => { chart1.hideCrosshair(); }; let chart2MouseMove = (args) => { if ((!Browser.isDevice && !chart2.isTouch && !chart2.isChartDrag) || chart2.startMove) { chart1.startMove = chart2.startMove; chart1.showCrosshair(args.x, args.y); } }; let chart2MouseUp = (args) => { if (Browser.isDevice && chart2.startMove) { chart1.hideCrosshair(); } }; return <div className="control-section"> <div className="row"> <div className="col"> <ChartComponent width="50%" id="container1" ref={chart => chart1 = chart} chartMouseLeave={chart1MouseLeave.bind(this)} chartMouseMove={chart1MouseMove.bind(this)} chartMouseUp={chart1MouseUp.bind(this)} crosshair={{ enable: true, lineType: 'Vertical', dashArray: '2,2' }} title="US to Euro"> <Inject services={[SplineSeries, DateTime, Crosshair]} /> <SeriesCollectionDirective> <SeriesDirective type="Spline" dataSource={synchronizedData} xName="USD" yName="EUR"></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> <div className="col"> <ChartComponent width="50%" id="container2" ref={chart => chart2 = chart} chartMouseLeave={chart2MouseLeave.bind(this)} chartMouseMove={chart2MouseMove.bind(this)} chartMouseUp={chart2MouseUp.bind(this)} crosshair={{ enable: true, lineType: 'Vertical', dashArray: '2,2' }} title="US to INR"> <Inject services={[AreaSeries, DateTime, Crosshair]} /> <SeriesCollectionDirective> <SeriesDirective type="Area" dataSource={synchronizedData} xName="USD" yName="INR" ></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> </div> </div> }; export default Candle; const root = createRoot(document.getElementById('sample')); root.render(<Candle />);
Refer to the following image.
Maintaining consistent zoom levels across several charts can significantly enhance the visual experience. To achieve this synchronization, you must leverage the zoomComplete event.
During this event, we should retrieve the zoomFactor and zoomPosition values specific to each chart. Subsequently, apply these obtained values to ensure uniformity across all your charts. This streamlines the viewing experience and ensures coherence in data analysis across multiple visual representations.
The following code example shows how to synchronize zooming across the charts.
import { createRoot } from 'react-dom/client'; import * as React from 'react'; import { useEffect } from 'react'; import { Chart, SplineAreaSeries, LineSeries, DateTime, Zoom, IZoomCompleteEventArgs, Selection, ChartComponent, SeriesCollectionDirective, SeriesDirective, Inject } from '@syncfusion/ej2-react-charts'; import { synchronizedData } from './financial-data'; import { Browser } from '@syncfusion/ej2-base'; export let zoomFactor; export let zoomPosition; export let pointColors = []; const Candle = () => { let chart1; let chart2; let charts = []; useEffect(() => { charts = [chart1, chart2]; }, []); let zoomFactor = 0; let zoomPosition = 0; let zoomComplete = (args) => { if (args.axis.name === 'primaryXAxis') { zoomFactor = args.currentZoomFactor; zoomPosition = args.currentZoomPosition; zoomCompleteFunction(args); } }; let zoomCompleteFunction = (args) => { for (let i = 0; i < charts.length; i++) { if (args.axis.series[0].chart.element.id !== charts[i].element.id) { charts[i].primaryXAxis.zoomFactor = zoomFactor; charts[i].primaryXAxis.zoomPosition = zoomPosition; charts[i].zoomModule.isZoomed = args.axis.series[0].chart.zoomModule.isZoomed; charts[i].zoomModule.isPanning = args.axis.series[0].chart.zoomModule.isPanning; } } } return <div className="control-section"> <div className="row"> <div className="col"> <ChartComponent id="container1" width="50%" ref={chart => chart1 = chart} zoomSettings={{ enableMouseWheelZooming: true, enablePinchZooming: true, enableScrollbar: false, enableDeferredZooming: false, enableSelectionZooming: true, enablePan: true, mode: 'X', toolbarItems: ['Pan', 'Reset'] }} zoomComplete={zoomComplete.bind(this)} titleStyle={{ textAlignment: 'Near' }} title="US to Euro"> <Inject services={[LineSeries, DateTime, Zoom, Selection]} /> <SeriesCollectionDirective> <SeriesDirective type="Line" dataSource={synchronizedData} xName="USD" yName="EUR" ></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> <div className="col"> <ChartComponent id="container2" ref={chart => chart2 = chart} width="50%" zoomSettings={{ enableMouseWheelZooming: true, enablePinchZooming: true, enableScrollbar: false, enableDeferredZooming: false, enableSelectionZooming: true, enablePan: true, mode: 'X', toolbarItems: ['Pan', 'Reset'] }} zoomComplete={zoomComplete.bind(this)} titleStyle={{ textAlignment: 'Near' }} title="US to INR"> <Inject services={[SplineAreaSeries, DateTime, Zoom, Selection]} /> <SeriesCollectionDirective> <SeriesDirective type="SplineArea" dataSource={synchronizedData} xName="USD" yName="INR"></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> </div> </div> }; export default Candle; const root = createRoot(document.getElementById('sample')); root.render(<Candle />);
Refer to the following output image.
You can highlight or select specific data points or regions of interest in one chart, and the corresponding data points or regions are then automatically highlighted or selected in other synchronized charts. This process can be achieved by using the selectionComplete event.
You can easily apply desired values to other charts by copying and pasting the values directly from this event to the respective chart.
Refer to the following code example to synchronize selection in charts.
import { createRoot } from 'react-dom/client'; import * as React from 'react'; import { useEffect } from 'react'; import { ColumnSeries, Zoom, IZoomCompleteEventArgs, Selection, ISelectionCompleteEventArgs, ChartComponent, SeriesCollectionDirective, SeriesDirective, Inject, Category } from '@syncfusion/ej2-react-charts'; import { synchronizedData } from './financial-data'; export let zoomFactor; export let zoomPosition; export let pointColors = []; const Candle = () => { let chart1; let chart2; let charts = []; useEffect(() => { charts = [chart1, chart2]; }, []); let zoomFactor = 0; let zoomPosition = 0; let count = 0; let zoomComplete = (args) => { if (args.axis.name === 'primaryXAxis') { zoomFactor = args.currentZoomFactor; zoomPosition = args.currentZoomPosition; zoomCompleteFunction(args); } }; let zoomCompleteFunction = (args) => { for (let i = 0; i < charts.length; i++) { if (args.axis.series[0].chart.element.id !== charts[i].element.id) { charts[i].primaryXAxis.zoomFactor = zoomFactor; charts[i].primaryXAxis.zoomPosition = zoomPosition; charts[i].zoomModule.isZoomed = args.axis.series[0].chart.zoomModule.isZoomed; charts[i].zoomModule.isPanning = args.axis.series[0].chart.zoomModule.isPanning; } } }; let selectionComplete = (args) => { selectionCompleteFunction(args); }; let selectionCompleteFunction = (args) => { if (count == 0) { for (var j = 0; j < args.selectedDataValues.length; j++) { args.selectedDataValues[j].point = args.selectedDataValues[j].pointIndex; args.selectedDataValues[j].series = args.selectedDataValues[j].seriesIndex; } for (var i = 0; i < charts.length; i++) { if (args.chart.element.id !== charts[i].element.id) { charts[i].selectedDataIndexes = args.selectedDataValues; count += 1; charts[i].dataBind(); } } count = 0; } }; let chartData = [ { x: 'GBR', y: 27 }, { x: 'CHN', y: 26 }, { x: 'AUS', y: 8 }, { x: 'RUS', y: 19 }, { x: 'GER', y: 17 }, { x: 'UA', y: 2 } ]; return<div className="control-section"> <div className="row"> <div className="col"> <ChartComponent id="container1" width="50%" ref={chart => chart1 = chart} selectionComplete={selectionComplete.bind(this)} title="US to Euro" selectionMode='Point' selectionPattern='Box'> <Inject services={[ColumnSeries, Category, Zoom, Selection]} /> <SeriesCollectionDirective> <SeriesDirective type="Column" dataSource={chartData} xName="x" yName="y" width={2} ></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> <div className="col"> <ChartComponent id="container2" width="50%" ref={chart => chart2 = chart} chartArea={{ border: { width: 0 } }} selectionComplete={selectionComplete.bind(this)} title="US to INR" selectionMode='Point' selectionPattern='Box'> <Inject services={[ColumnSeries, Category, Zoom, Selection]} /> <SeriesCollectionDirective> <SeriesDirective type="Column" dataSource={chartData} xName="x" yName="y" width={2} ></SeriesDirective> </SeriesCollectionDirective> </ChartComponent> </div> </div> </div> }; export default Candle; const root = createRoot(document.getElementById('sample')); root.render(<Candle />);
Refer to the following output image.
For more details, refer to the synchronized React Charts documentation and demo.
Thanks for reading! In this blog, we explored how to synchronize elements in the Syncfusion React Charts to compare multiple data sets. Try out this powerful tool and share your feedback in the comments section.
The new version of Essential Studio® is available on the License and Downloads page for our existing customers. If you’re new to Syncfusion, we’re offering a 30-day free trial to experience the wide array of features we provide.
You can also contact us via our support forum, support portal, or feedback portal. We are always eager to help you!