Synchronized Charts in JavaScript Chart control
18 Nov 201824 minutes to read
Synchronized charts allow multiple chart instances to share common interactions so that actions performed on one chart are reflected across the others. This approach is useful for comparing related datasets and analyzing trends consistently across multiple visualizations.
Tooltip synchronization
The tooltip can be synchronized across multiple charts by using the showTooltip and hideTooltip methods. When the user hovers over a data point in one chart, the showTooltip method can be invoked for the other charts to display the corresponding tooltip information simultaneously.
In the showTooltip method, specify the following parameters programmatically to enable the tooltip for a specific chart:
-
x– The x-value of the data point or the x-coordinate. -
y– The y-value of the data point or the y-coordinate.
var charts = [];
var chart = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1
},
primaryYAxis: {
labelFormat: 'n2',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 0.86,
maximum: 0.96,
interval: 0.025
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'Line', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'EUR', emptyPointSettings: { mode: 'Drop' }
}
],
chartMouseLeave: function (args) {
chart1.hideTooltip();
},
chartMouseMove: function (args) {
if ((!ej.base.Browser.isDevice && !chart.isTouch && !chart.isChartDrag) || chart.startMove) {
chart1.startMove = chart.startMove;
chart1.showTooltip(args.x, args.y);
}
},
chartMouseUp: function (args) {
if (ej.base.Browser.isDevice && chart.startMove) {
chart1.hideTooltip();
}
},
title: 'US to EURO',
titleStyle: { textAlignment: 'Near' },
tooltip: { enable: true, fadeOutDuration: ej.base.Browser.isDevice ? 2500 : 1000, shared: true, header: '', format: '<b>€${point.y}</b> <br> ${point.x} 2023', enableMarker: false },
});
chart.appendTo('#container1');
charts.push(chart);
var chart1 = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1
},
primaryYAxis: {
labelFormat: 'n1',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 79,
maximum: 85,
interval: 1.5
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'Area', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'INR', opacity: 0.6, border: { width: 2 }
}
],
chartMouseMove: function (args) {
if ((!ej.base.Browser.isDevice && !chart1.isTouch && !chart1.isChartDrag) || chart1.startMove) {
chart.startMove = chart1.startMove;
chart.showTooltip(args.x, args.y);
}
},
chartMouseLeave: function (args) {
chart.hideTooltip();
},
chartMouseUp: function (args) {
if (ej.base.Browser.isDevice && chart1.startMove) {
chart.hideTooltip();
}
},
title: 'US to INR',
titleStyle: { textAlignment: 'Near' },
tooltip: { enable: true, fadeOutDuration: ej.base.Browser.isDevice ? 2500 : 1000, shared: true, header: '', format: '<b>₹${point.y}</b> <br> ${point.x} 2023', enableMarker: false },
});
chart1.appendTo('#container2');
charts.push(chart1);<!DOCTYPE html><html lang="en"><head>
<title>EJ2 Animation</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Typescript UI Controls">
<meta name="author" content="Syncfusion">
<link href="index.css" rel="stylesheet">
<script src="es5-datasource.js" type="text/javascript"></script>
<script src="https://cdn.syncfusion.com/ej2/34.1.29/dist/ej2.min.js" type="text/javascript"></script>
</head>
<style>
#control-container {
padding: 1px !important;
}
.row {
display: flex;
}
.col {
width: 50%;
margin: 10px;
height: 270px;
}
</style>
<body>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
<script>
var ele = document.getElementById('container');
if(ele) {
ele.style.visibility = "visible";
}
</script>
<script src="index.js" type="text/javascript"></script>
</body></html>Crosshair synchronization
The crosshair can be synchronized across multiple charts by using the showCrosshair and hideCrosshair methods. When the user moves the pointer over one chart, calling the showCrosshair method on the other charts aligns the crosshair position across all synchronized charts, making data comparison easier.
In the showCrosshair method, specify the following parameters to render the crosshair for a particular chart:
-
x– Specifies the x-value of the data point or the x-coordinate. -
y– Specifies the y-value of the data point or the y-coordinate.
var charts = [];
var chart = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1,
crosshairTooltip: { enable: true }
},
primaryYAxis: {
labelFormat: 'n2',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 0.86,
maximum: 0.96,
interval: 0.025
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'Spline', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'EUR', emptyPointSettings: { mode: 'Drop' }
}
],
chartMouseLeave: function (args) {
chart1.hideCrosshair();
},
chartMouseMove: function (args) {
if ((!ej.base.Browser.isDevice && !chart.isTouch && !chart.isChartDrag) || chart.startMove) {
chart1.startMove = chart.startMove;
chart1.showCrosshair(args.x, args.y);
}
},
chartMouseUp: function (args) {
if (ej.base.Browser.isDevice && chart.startMove) {
chart1.hideCrosshair();
}
},
title: 'US to EURO',
titleStyle: { textAlignment: 'Near' },
crosshair: { enable: true, lineType: 'Vertical', dashArray: '2,2' }
});
chart.appendTo('#container1');
charts.push(chart);
var chart1 = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1,
crosshairTooltip: { enable: true }
},
primaryYAxis: {
labelFormat: 'n1',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 79,
maximum: 85,
interval: 1.5
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'Area', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'INR', opacity: 0.6, border: { width: 2 }
}
],
chartMouseMove: function (args) {
if ((!ej.base.Browser.isDevice && !chart1.isTouch && !chart1.isChartDrag) || chart1.startMove) {
chart.startMove = chart1.startMove;
chart.showCrosshair(args.x, args.y);
}
},
chartMouseLeave: function (args) {
chart.hideCrosshair();
},
chartMouseUp: function (args) {
if (ej.base.Browser.isDevice && chart1.startMove) {
chart.hideCrosshair();
}
},
title: 'US to INR',
titleStyle: { textAlignment: 'Near' },
crosshair: { enable: true, lineType: 'Vertical', dashArray: '2,2' },
});
chart1.appendTo('#container2');
charts.push(chart1);<!DOCTYPE html><html lang="en"><head>
<title>EJ2 Animation</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Typescript UI Controls">
<meta name="author" content="Syncfusion">
<link href="index.css" rel="stylesheet">
<script src="es5-datasource.js" type="text/javascript"></script>
<script src="https://cdn.syncfusion.com/ej2/34.1.29/dist/ej2.min.js" type="text/javascript"></script>
</head>
<style>
#control-container {
padding: 1px !important;
}
.row {
display: flex;
}
.col {
width: 50%;
margin: 10px;
height: 270px;
}
</style>
<body>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
<script>
var ele = document.getElementById('container');
if(ele) {
ele.style.visibility = "visible";
}
</script>
<script src="index.js" type="text/javascript"></script>
</body></html>Zooming synchronization
Zoom levels can be synchronized across multiple charts by using the zoomComplete event. In the zoomComplete event, retrieve the zoomFactor and zoomPosition values from the zoomed chart.
var charts = [];
var zoomFactor = 0;
var zoomPosition = 0;
var chart = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1
},
primaryYAxis: {
labelFormat: 'n2',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 0.86,
maximum: 0.96,
interval: 0.025
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'Line', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'EUR', emptyPointSettings: { mode: 'Drop' }
}
],
zoomSettings: {
enableMouseWheelZooming: true,
enablePinchZooming: true,
enableScrollbar: false,
enableDeferredZooming: false,
enableSelectionZooming: true,
enablePan: true,
mode: 'X',
toolbarItems: ['Pan', 'Reset']
},
zoomComplete: function (args) {
if (args.axis.name === 'primaryXAxis') {
zoomFactor = args.currentZoomFactor;
zoomPosition = args.currentZoomPosition;
zoomCompleteFunction(args);
}
},
title: 'US to EURO',
titleStyle: { textAlignment: 'Near' }
});
chart.appendTo('#container1');
charts.push(chart);
var chart1 = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1
},
primaryYAxis: {
labelFormat: 'n1',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 79,
maximum: 85,
interval: 1.5
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'SplineArea', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'INR', opacity: 0.6, border: { width: 2 }
}
],
zoomSettings: {
enableMouseWheelZooming: true,
enablePinchZooming: true,
enableScrollbar: false,
enableDeferredZooming: false,
enableSelectionZooming: true,
enablePan: true,
mode: 'X',
toolbarItems: ['Pan', 'Reset']
},
zoomComplete: function (args) {
if (args.axis.name === 'primaryXAxis') {
zoomFactor = args.currentZoomFactor;
zoomPosition = args.currentZoomPosition;
zoomCompleteFunction(args);
}
},
title: 'US to INR',
titleStyle: { textAlignment: 'Near' }
});
chart1.appendTo('#container2');
charts.push(chart1);
function zoomCompleteFunction(args) {
for (var 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;
}
}
}<!DOCTYPE html><html lang="en"><head>
<title>EJ2 Animation</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Typescript UI Controls">
<meta name="author" content="Syncfusion">
<link href="index.css" rel="stylesheet">
<script src="es5-datasource.js" type="text/javascript"></script>
<script src="https://cdn.syncfusion.com/ej2/34.1.29/dist/ej2.min.js" type="text/javascript"></script>
</head>
<style>
#control-container {
padding: 1px !important;
}
.row {
display: flex;
}
.col {
width: 50%;
margin: 10px;
height: 270px;
}
</style>
<body>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
<script>
var ele = document.getElementById('container');
if(ele) {
ele.style.visibility = "visible";
}
</script>
<script src="index.js" type="text/javascript"></script>
</body></html>Selection synchronization
Selection can be synchronized across multiple charts by using the selectionComplete event. In the selectionComplete event, retrieve the selected data values or region from the active chart and apply the same selection state to the other charts.
var charts = [];
var zoomFactor = 0;
var zoomPosition = 0;
var count = 0;
var chart = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1
},
primaryYAxis: {
labelFormat: 'n2',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 0.86,
maximum: 0.96,
interval: 0.025
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'Line', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'EUR', emptyPointSettings: { mode: 'Drop' }
}
],
zoomSettings: {
enableSelectionZooming: true,
mode: 'X'
},
zoomComplete: function (args) {
if (args.axis.name === 'primaryXAxis') {
zoomFactor = args.currentZoomFactor;
zoomPosition = args.currentZoomPosition;
zoomCompleteFunction(args);
}
},
selectionComplete: function (args) {
selectionCompleteFunction(args);
},
selectionPattern: 'Box',
selectionMode: 'Point',
title: 'US to EURO',
titleStyle: { textAlignment: 'Near' }
});
chart.appendTo('#container1');
charts.push(chart);
var chart1 = new ej.charts.Chart({
primaryXAxis: {
minimum: new Date(2023, 1, 18),
maximum: new Date(2023, 7, 18),
valueType: 'DateTime',
labelFormat: 'MMM d',
lineStyle: { width: 0 },
majorGridLines: { width: 0 },
edgeLabelPlacement: ej.base.Browser.isDevice ? 'None' : 'Shift',
labelRotation: ej.base.Browser.isDevice ? -45 : 0,
interval: ej.base.Browser.isDevice ? 2 : 1
},
primaryYAxis: {
labelFormat: 'n1',
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
minimum: 79,
maximum: 85,
interval: 1.5
},
chartArea: { border: { width: 0 } },
series: [
{
type: 'Spline', dataSource: synchronizedData, xName: 'USD', width: 2, yName: 'INR', border: { width: 2 }
}
],
zoomSettings: {
enableSelectionZooming: true,
mode: 'X'
},
zoomComplete: function (args) {
if (args.axis.name === 'primaryXAxis') {
zoomFactor = args.currentZoomFactor;
zoomPosition = args.currentZoomPosition;
zoomCompleteFunction(args);
}
},
selectionComplete: function (args) {
selectionCompleteFunction(args);
},
selectionPattern: 'Box',
selectionMode: 'Point',
title: 'US to INR',
titleStyle: { textAlignment: 'Near' }
});
chart1.appendTo('#container2');
charts.push(chart1);
function zoomCompleteFunction(args) {
for (var 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;
}
}
}
function 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;
}
}<!DOCTYPE html><html lang="en"><head>
<title>EJ2 Animation</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Typescript UI Controls">
<meta name="author" content="Syncfusion">
<link href="index.css" rel="stylesheet">
<script src="es5-datasource.js" type="text/javascript"></script>
<script src="https://cdn.syncfusion.com/ej2/34.1.29/dist/ej2.min.js" type="text/javascript"></script>
</head>
<style>
#control-container {
padding: 1px !important;
}
.row {
display: flex;
}
.col {
width: 50%;
margin: 10px;
height: 270px;
}
</style>
<body>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
<script>
var ele = document.getElementById('container');
if(ele) {
ele.style.visibility = "visible";
}
</script>
<script src="index.js" type="text/javascript"></script>
</body></html>