React Synchronized Charts: The Perfect Tool to Compare Multiple Datasets

Lucy Muturi - Aug 19 - - Dev Community

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.

Uses of synchronized charts

The use cases of synchronized charts are as follows:

  1. Correlation analysis: Unveiling interconnected relationships within data.
  2. Holistic view: Enabling a comprehensive understanding by amalgamating multiple perspectives.
  3. Real-time analysis: Empowering instantaneous insights into evolving trends.
  4. Comparative analysis: Facilitating side-by-side comparisons for insightful observations.
  5. Improved decision-making: Empowering informed and strategic choices.
  6. Enhanced user experience: Elevating the accessibility and usability of data.

Synchronizing chart elements in React Charts

Let’s seamlessly analyze market data (relationship between dollar value changes for different currencies) by configuring the following elements in the React Charts:

  • Tooltip
  • Crosshair
  • Zooming
  • Selection

Tooltip synchronization in 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:

  • x: Represents the x-value of the point.
  • y: Represents the y-value of the point.

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 />);
Enter fullscreen mode Exit fullscreen mode

Refer to the following output image.

Synchronizing tooltips in React Charts

Synchronizing tooltips in React Charts

Crosshair synchronization in React Charts

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:

  • x: Represents the x-coordinate value of the point.
  • y: Represents the y-coordinate value of the point.

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 />);
Enter fullscreen mode Exit fullscreen mode

Refer to the following image.

Crosshair synchronization in React Charts

Crosshair synchronization in React Charts

Zooming synchronization in React Charts

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 />);
Enter fullscreen mode Exit fullscreen mode

Refer to the following output image.

Zooming synchronization in React Charts

Zooming synchronization in React Charts

Selection synchronization in React Charts

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 />);
Enter fullscreen mode Exit fullscreen mode

Refer to the following output image.

Selection synchronizing in React Charts

Selection synchronizing in React Charts

References

For more details, refer to the synchronized React Charts documentation and demo.

Conclusion

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!

Related blogs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .