Synchronized Charts in EJ2 TypeScript Chart control
4 Oct 202324 minutes to read
Tooltip synchronization
The tooltip can be synchronized across multiple charts using the showTooltip
and hideTooltip
methods. When we hover over a data point in one chart, we call the showTooltip
method for the other charts to display related information in other connected charts simultaneously.
In the showTooltip
method, specify the following parameters programmatically to enable tooltip for a particular chart:
-
x
- Data point x-value or x-coordinate value. -
y
- Data point y-value or y-coordinate value.
import { Chart, AreaSeries, LineSeries, DateTime, Tooltip, IMouseEventArgs, ITooltipRenderEventArgs, ILegendClickEventArgs } from '@syncfusion/ej2-charts';
Chart.Inject(AreaSeries, LineSeries, DateTime, Tooltip);
import { Browser } from '@syncfusion/ej2-base';
import { synchronizedData } from './datasource.ts';
let charts: Chart[] = [];
let chart: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IMouseEventArgs) => {
chart1.hideTooltip();
},
chartMouseMove: (args: IMouseEventArgs) => {
if ((!Browser.isDevice && !chart.isTouch && !chart.isChartDrag) || chart.startMove) {
chart1.startMove = chart.startMove;
chart1.showTooltip(args.x, args.y);
}
},
chartMouseUp: function (args: IMouseEventArgs) {
if (Browser.isDevice && chart.startMove) {
chart1.hideTooltip();
}
},
title: 'US to EURO',
titleStyle: { textAlignment: 'Near' },
tooltip: { enable: true, fadeOutDuration: Browser.isDevice ? 2500 : 1000, shared: true, header: '', format: '<b>€${point.y}</b> <br> ${point.x} 2023', enableMarker: false },
});
chart.appendTo('#container1');
charts.push(chart);
let chart1: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IMouseEventArgs) => {
if ((!Browser.isDevice && !chart1.isTouch && !chart1.isChartDrag) || chart1.startMove) {
chart.startMove = chart1.startMove;
chart.showTooltip(args.x, args.y);
}
},
chartMouseLeave: (args: IMouseEventArgs) => {
chart.hideTooltip();
},
chartMouseUp: function (args: IMouseEventArgs) {
if (Browser.isDevice || chart1.startMove) {
chart.hideTooltip();
}
},
title: 'US to INR',
titleStyle: { textAlignment: 'Near' },
tooltip: { enable: true, fadeOutDuration: 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="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
<script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.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 id='loader'>Loading....</div>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
</body>
</html>
Crosshair synchronization
The crosshair can be synchronized across multiple charts using the showCrosshair
and hideCrosshair
methods. When we hover over one chart, we call the showCrosshair
method for the other charts to align with data points in other connected charts, simplifying data comparison and analysis.
In the showCrosshair
method, specify the following parameters programmatically to enable crosshair for a particular chart:
-
x
- Specifies the x-value of the point or x-coordinate. -
y
- Specifies the y-value of the point or y-coordinate.
import { Chart, AreaSeries, SplineSeries, DateTime, Crosshair, IMouseEventArgs } from '@syncfusion/ej2-charts';
Chart.Inject(AreaSeries, SplineSeries, DateTime, Crosshair);
import { Browser } from '@syncfusion/ej2-base';
import { synchronizedData } from './datasource.ts';
let charts: Chart[] = [];
let chart: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IMouseEventArgs) => {
chart1.hideCrosshair();
},
chartMouseMove: (args: IMouseEventArgs) => {
if ((!Browser.isDevice && !chart.isTouch && !chart.isChartDrag) || chart.startMove) {
chart1.startMove = chart.startMove;
chart1.showCrosshair(args.x, args.y);
}
},
chartMouseUp: function (args: IMouseEventArgs) {
if (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);
let chart1: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IMouseEventArgs) => {
if ((!Browser.isDevice && !chart1.isTouch && !chart1.isChartDrag) || chart1.startMove) {
chart.startMove = chart1.startMove;
chart.showCrosshair(args.x, args.y);
}
},
chartMouseLeave: (args: IMouseEventArgs) => {
chart.hideCrosshair();
},
chartMouseUp: function (args: IMouseEventArgs) {
if (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="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
<script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.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 id='loader'>Loading....</div>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
</body>
</html>
Zooming synchronization
You can maintain constant zoom levels across multiple charts using the zoomComplete
event. In the zoomComplete
event, obtain the zoomFactor
and zoomPosition
values of the particular chart, and then apply those values to the other charts.
import { Chart, SplineAreaSeries, LineSeries, DateTime, Zoom, ZoomSettings, IZoomCompleteEventArgs, IMouseEventArgs, ITooltipRenderEventArgs, ILegendClickEventArgs } from '@syncfusion/ej2-charts';
Chart.Inject(SplineAreaSeries, LineSeries, DateTime, Zoom);
import { Browser } from '@syncfusion/ej2-base';
import { synchronizedData } from './datasource.ts';
import { Axis } from '@syncfusion/ej2/charts';
let charts: Chart[] = [];
let zoomFactor: number = 0;
let zoomPosition: number = 0;
let chart: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IZoomCompleteEventArgs) => {
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);
let chart1: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IZoomCompleteEventArgs) => {
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: IZoomCompleteEventArgs): void {
for (let i: number = 0; i < charts.length; i++) {
if ((args.axis as 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 as Axis).series[0].chart.zoomModule.isZoomed;
charts[i].zoomModule.isPanning = (args.axis as 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="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
<script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.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 id='loader'>Loading....</div>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
</body>
</html>
Selection synchronization
You can select the data across multiple charts using the selectionComplete
event. In the selectionComplete
event, obtain the selected values of the particular chart, and then apply those values to the other charts.
import { Chart, SplineSeries, LineSeries, DateTime, Highlight, Zoom, ZoomSettings, IZoomCompleteEventArgs, Selection, ISelectionCompleteEventArgs, IMouseEventArgs, ITooltipRenderEventArgs, ILegendClickEventArgs } from '@syncfusion/ej2-charts';
Chart.Inject(SplineSeries, LineSeries, DateTime, Zoom, Highlight, Selection);
import { Browser } from '@syncfusion/ej2-base';
import { synchronizedData } from './datasource.ts';
import { Axis } from '@syncfusion/ej2/charts';
let charts: Chart[] = [];
let zoomFactor: number = 0;
let zoomPosition: number = 0;
let count: number = 0;
let chart: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IZoomCompleteEventArgs) => {
if (args.axis.name === 'primaryXAxis') {
zoomFactor = args.currentZoomFactor;
zoomPosition = args.currentZoomPosition;
zoomCompleteFunction(args);
}
},
selectionComplete: function (args: ISelectionCompleteEventArgs | any) {
selectionCompleteFunction(args);
},
selectionPattern: 'Box',
selectionMode: 'Point',
title: 'US to EURO',
titleStyle: { textAlignment: 'Near' }
});
chart.appendTo('#container1');
charts.push(chart);
let chart1: Chart = new 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: Browser.isDevice ? 'None' : 'Shift',
labelRotation: Browser.isDevice ? -45 : 0,
interval: 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: (args: IZoomCompleteEventArgs) => {
if (args.axis.name === 'primaryXAxis') {
zoomFactor = args.currentZoomFactor;
zoomPosition = args.currentZoomPosition;
zoomCompleteFunction(args);
}
},
selectionComplete: function (args: ISelectionCompleteEventArgs | any) {
selectionCompleteFunction(args);
},
selectionPattern: 'Box',
selectionMode: 'Point',
title: 'US to INR',
titleStyle: { textAlignment: 'Near' }
});
chart1.appendTo('#container2');
charts.push(chart1);
function zoomCompleteFunction(args: IZoomCompleteEventArgs): void {
for (let i: number = 0; i < charts.length; i++) {
if ((args.axis as 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 as Axis).series[0].chart.zoomModule.isZoomed;
charts[i].zoomModule.isPanning = (args.axis as Axis).series[0].chart.zoomModule.isPanning;
}
}
}
function selectionCompleteFunction(args: ISelectionCompleteEventArgs | any): void {
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="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
<script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.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 id='loader'>Loading....</div>
<div class="control-section">
<div class="row">
<div class="col" id="container1"></div>
<div class="col" id="container2"></div>
</div>
</div>
</body>
</html>