Synchronized Charts in Angular Chart component

27 Apr 202424 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 { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { DateTimeService, AreaSeriesService, LineSeriesService } from '@syncfusion/ej2-angular-charts'
import { TooltipService } from '@syncfusion/ej2-angular-charts'




import { Component, ViewChild, OnInit } from '@angular/core';
import { IMouseEventArgs, ChartComponent } from '@syncfusion/ej2-angular-charts';
import { synchronizedData } from './datasource';
import { Browser } from '@syncfusion/ej2-base';
@Component({
imports: [
         ChartModule
    ],

providers: [ DateTimeService, AreaSeriesService, LineSeriesService, TooltipService ],
standalone: true,
    selector: 'app-container',
    template: `<div class="control-section">
    <div class="row">
        <div class="col" >
            <ejs-chart #chart1 style='display:block;' id="container1" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis1'
                [title]='title1' [titleStyle]="titleStyle" [tooltip]="tooltip1"
                (chartMouseLeave)= 'chart1MouseLeave($event)' (chartMouseMove)='chart1MouseMove($event)' (chartMouseUp)='chart1MouseUp($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='Line' xName='USD' yName='EUR' [width]="width">
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
        <div class="col" >
            <ejs-chart #chart2 style='display:block;' id="container2" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis2'
                [title]='title2' [titleStyle]="titleStyle" [tooltip]="tooltip2" 
                (chartMouseLeave)= 'chart2MouseLeave($event)' (chartMouseMove)='chart2MouseMove($event)' (chartMouseUp)='chart2MouseUp($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='Area' xName='USD' yName='INR' opacity=0.6
                        [width]="width" [border]='border'>
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
    </div>
</div>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public primaryYAxis1?: Object
    public primaryYAxis2?: Object;
    public chartData?: Object[];
    public title1?: string;
    public title2?: string;
    public titleStyle?: Object;
    public tooltip1?: Object;
    public tooltip2?: Object;
    public border?: Object;
    public width?: number;
    @ViewChild('chart1')
    public chart1: ChartComponent;

    @ViewChild('chart2')
    public chart2: ChartComponent;

    public chart1MouseLeave(args: IMouseEventArgs): void {
        this.chart2.hideTooltip();
    };
    public chart1MouseMove(args: IMouseEventArgs): void {
        if ((!Browser.isDevice && !this.chart1.isTouch && !this.chart1.isChartDrag) || this.chart1.startMove) {
            this.chart2.startMove = this.chart1.startMove;
            this.chart2.showTooltip(args.x, args.y);
        }
    };
    public chart1MouseUp(args: IMouseEventArgs): void {
        if (Browser.isDevice && this.chart1.startMove) {
            this.chart2.hideTooltip();
        }
    };
    public chart2MouseLeave(args: IMouseEventArgs): void {
        this.chart1.hideTooltip();
    };
    public chart2MouseMove(args: IMouseEventArgs): void {
        if ((!Browser.isDevice && !this.chart2.isTouch && !this.chart2.isChartDrag) || this.chart2.startMove) {
            this.chart2.startMove = this.chart1.startMove;
            this.chart1.showTooltip(args.x, args.y);
        }
    };
    public chart2MouseUp(args: IMouseEventArgs): void {
        if (Browser.isDevice && this.chart2.startMove) {
            this.chart1.hideTooltip();
        }
    };
    ngOnInit(): void {
        this.chartData = synchronizedData;
        this.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
        };
        this.primaryYAxis1 = {
            labelFormat: 'n2',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 0.86,
            maximum: 0.96,
            interval: 0.025
        };
        this.primaryYAxis2 = {
            labelFormat: 'n1',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 79,
            maximum: 85,
            interval: 1.5
        };
        this.title1 = 'US to Euro';
        this.title2 = 'US to INR';
        this.tooltip1 = {
            enable: true, fadeOutDuration: Browser.isDevice ? 2500 : 1000, shared: true, header: '', format: '<b>€${point.y}</b><br>${point.x} 2023', enableMarker: false
        };
        this.tooltip2 = {
            enable: true, fadeOutDuration: Browser.isDevice ? 2500 : 1000, shared: true, header: '', format: '<b>₹${point.y}</b><br>${point.x} 2023', enableMarker: false
        };
        this.border = {
            width: 2
        };
        this.width = 2;
        this.titleStyle = {
            textAlignment: 'Near'
        };
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

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 { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { DateTimeService, AreaSeriesService, SplineSeriesService } from '@syncfusion/ej2-angular-charts'
import { CrosshairService } from '@syncfusion/ej2-angular-charts'




import { Component, ViewChild, OnInit } from '@angular/core';
import { IMouseEventArgs, ChartComponent } from '@syncfusion/ej2-angular-charts';
import { synchronizedData } from './datasource';
import { Browser } from '@syncfusion/ej2-base';
@Component({
imports: [
         ChartModule
    ],

providers: [ DateTimeService, AreaSeriesService, SplineSeriesService, CrosshairService],
standalone: true,
    selector: 'app-container',
    template: `<div class="control-section">
    <div class="row">
        <div class="col" >
            <ejs-chart #chart1 style='display:block;' id="container1" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis1'
                [title]='title1' [titleStyle]="titleStyle" [crosshair]='crosshair'
                (chartMouseLeave)= 'chart1MouseLeave($event)' (chartMouseMove)='chart1MouseMove($event)' (chartMouseUp)='chart1MouseUp($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='Spline' xName='USD' yName='EUR' [width]="width">
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
        <div class="col" >
            <ejs-chart #chart2 style='display:block;' id="container2" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis2'
                [title]='title2' [titleStyle]="titleStyle" [crosshair]='crosshair'
                (chartMouseLeave)= 'chart2MouseLeave($event)' (chartMouseMove)='chart2MouseMove($event)' (chartMouseUp)='chart2MouseUp($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='Area' xName='USD' yName='INR' opacity=0.6
                        [width]="width" [border]='border'>
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
    </div>
</div>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public primaryYAxis1?: Object
    public primaryYAxis2?: Object;
    public chartData?: Object[];
    public title1?: string;
    public title2?: string;
    public titleStyle?: Object;
    public border?: Object;
    public width?: number;
    public crosshair?: Object;
    @ViewChild('chart1')
    public chart1: ChartComponent;

    @ViewChild('chart2')
    public chart2: ChartComponent;

    public chart1MouseLeave(args: IMouseEventArgs): void {
        this.chart2.hideCrosshair();
    };
    public chart1MouseMove(args: IMouseEventArgs): void {
        if ((!Browser.isDevice && !this.chart1.isTouch && !this.chart1.isChartDrag) || this.chart1.startMove) {
            this.chart2.startMove = this.chart1.startMove;
            this.chart2.showCrosshair(args.x, args.y);
        }
    };
    public chart1MouseUp(args: IMouseEventArgs): void {
        if (Browser.isDevice && this.chart1.startMove) {
            this.chart2.hideCrosshair();
        }
    };
    public chart2MouseLeave(args: IMouseEventArgs): void {
        this.chart1.hideCrosshair();
    };
    public chart2MouseMove(args: IMouseEventArgs): void {
        if ((!Browser.isDevice && !this.chart2.isTouch && !this.chart2.isChartDrag) || this.chart2.startMove) {
            this.chart2.startMove = this.chart1.startMove;
            this.chart1.showCrosshair(args.x, args.y);
        }
    };
    public chart2MouseUp(args: IMouseEventArgs): void {
        if (Browser.isDevice && this.chart2.startMove) {
            this.chart1.hideCrosshair();
        }
    };
    ngOnInit(): void {
        this.chartData = synchronizedData;
        this.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 },
        };
        this.primaryYAxis1 = {
            labelFormat: 'n2',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 0.86,
            maximum: 0.96,
            interval: 0.025
        };
        this.primaryYAxis2 = {
            labelFormat: 'n1',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 79,
            maximum: 85,
            interval: 1.5
        };
        this.title1 = 'US to Euro';
        this.title2 = 'US to INR';
        this.border = {
            width: 2
        };
        this.width = 2;
        this.titleStyle = {
            textAlignment: 'Near'
        };
        this.crosshair = {
            enable: true, lineType: 'Vertical', dashArray: '2,2'
        };
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

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 { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { DateTimeService, LineSeriesService, SplineAreaSeriesService } from '@syncfusion/ej2-angular-charts'
import { ZoomService } from '@syncfusion/ej2-angular-charts'




import { Component, ViewChild, OnInit } from '@angular/core';
import { IMouseEventArgs, ChartComponent, IZoomCompleteEventArgs } from '@syncfusion/ej2-angular-charts';
import { synchronizedData } from './datasource';
import { Browser } from '@syncfusion/ej2-base';
@Component({
imports: [
         ChartModule
    ],

providers: [ DateTimeService, LineSeriesService, SplineAreaSeriesService, ZoomService],
standalone: true,
    selector: 'app-container',
    template: `<div class="control-section">
    <div class="row">
        <div class="col" >
            <ejs-chart #chart1 style='display:block;' id="container1" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis1'
                [title]='title1' [titleStyle]="titleStyle" [zoomSettings]='zoomSettings' (zoomComplete)='zoomComplete($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='Line' xName='USD' yName='EUR' [width]="width">
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
        <div class="col" >
            <ejs-chart #chart2 style='display:block;' id="container2" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis2'
                [title]='title2' [titleStyle]="titleStyle" [zoomSettings]='zoomSettings' (zoomComplete)='zoomComplete($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='SplineArea' xName='USD' yName='INR' opacity=0.6
                        [width]="width" [border]='border'>
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
    </div>
</div>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public primaryYAxis1?: Object
    public primaryYAxis2?: Object;
    public chartData?: Object[];
    public title1?: string;
    public title2?: string;
    public titleStyle?: Object;
    public border?: Object;
    public width?: number;
    public zoomSettings?: Object;
    @ViewChild('chart1')
    public chart1!: ChartComponent;
    @ViewChild('chart2')
    public chart2!: ChartComponent;
    public charts: ChartComponent[] = [];
    public zoomFactor: number = 0;
    public zoomPosition: number = 0;
    ngAfterViewInit() {
        this.charts = [this.chart1, this.chart2];
    }
    public chart1MouseLeave(args: IMouseEventArgs): void {
        this.chart2.hideTooltip();
    };
    public zoomComplete(args: IZoomCompleteEventArgs): void {
        if (args.axis.name === 'primaryXAxis') {
            this.zoomFactor = args.currentZoomFactor;
            this.zoomPosition = args.currentZoomPosition;
            this.zoomCompleteFunction(args);
        }
    };
    public zoomCompleteFunction(args: any): void {
        for (let i: number = 0; i < this.charts.length; i++) {
            if (args.axis.series[0].chart.element.id !== this.charts[i].element.id) {
                this.charts[i].primaryXAxis.zoomFactor = this.zoomFactor;
                this.charts[i].primaryXAxis.zoomPosition = this.zoomPosition;
                this.charts[i].zoomModule.isZoomed = args.axis.series[0].chart.zoomModule.isZoomed;
                this.charts[i].zoomModule.isPanning = args.axis.series[0].chart.zoomModule.isPanning;
            }
        }
    };

    ngOnInit(): void {
        this.chartData = synchronizedData;
        this.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
        };
        this.primaryYAxis1 = {
            labelFormat: 'n2',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 0.86,
            maximum: 0.96,
            interval: 0.025
        };
        this.primaryYAxis2 = {
            labelFormat: 'n1',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 79,
            maximum: 85,
            interval: 1.5
        };
        this.title1 = 'US to Euro';
        this.title2 = 'US to INR';
        this.zoomSettings = {
            enableMouseWheelZooming: true,
            enablePinchZooming: true,
            enableScrollbar: false,
            enableDeferredZooming: false,
            enableSelectionZooming: true,
            enablePan: true,
            mode: 'X',
            toolbarItems: ['Pan', 'Reset']
        };
        this.border = {
            width: 2
        };
        this.width = 2;
        this.titleStyle = {
            textAlignment: 'Near'
        };
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

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 { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ChartModule } from '@syncfusion/ej2-angular-charts'
import { DateTimeService, LineSeriesService, SplineSeriesService } from '@syncfusion/ej2-angular-charts'
import { SelectionService, ZoomService } from '@syncfusion/ej2-angular-charts'




import { Component, ViewChild, OnInit } from '@angular/core';
import { IMouseEventArgs, ChartComponent, IZoomCompleteEventArgs, ISelectionCompleteEventArgs } from '@syncfusion/ej2-angular-charts';
import { synchronizedData } from './datasource';
import { Browser } from '@syncfusion/ej2-base';
@Component({
imports: [
         ChartModule
    ],

providers: [ DateTimeService, LineSeriesService, SplineSeriesService, SelectionService, ZoomService],
standalone: true,
    selector: 'app-container',
    template: `<div class="control-section">
    <div class="row">
        <div class="col" >
            <ejs-chart #chart1 style='display:block;' id="container1" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis1'
                [title]='title1' [titleStyle]="titleStyle" [zoomSettings]='zoomSettings' (zoomComplete)='zoomComplete($event)' selectionMode='Point' selectionPattern='Box' (selectionComplete)='selectionComplete($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='Line' xName='USD' yName='EUR' [width]="width">
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
        <div class="col" >
            <ejs-chart #chart2 style='display:block;' id="container2" [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis2'
                [title]='title2' [titleStyle]="titleStyle" [zoomSettings]='zoomSettings' (zoomComplete)='zoomComplete($event)' selectionMode='Point' selectionPattern='Box' (selectionComplete)='selectionComplete($event)'>
                <e-series-collection>
                    <e-series [dataSource]='chartData' type='Spline' xName='USD' yName='INR'
                        [width]="width" [border]='border'>
                    </e-series>
                </e-series-collection>
            </ejs-chart>
        </div>
    </div>
</div>`
})
export class AppComponent implements OnInit {
    public primaryXAxis?: Object;
    public primaryYAxis1?: Object
    public primaryYAxis2?: Object;
    public chartData?: Object[];
    public title1?: string;
    public title2?: string;
    public titleStyle?: Object;
    public border?: Object;
    public width?: number;
    public zoomSettings?: Object;
    @ViewChild('chart1')
    public chart1!: ChartComponent;
    @ViewChild('chart2')
    public chart2!: ChartComponent;
    public charts: ChartComponent[] = [];
    public zoomFactor: number = 0;
    public zoomPosition: number = 0;
    public count: number = 0;
    ngAfterViewInit() {
        this.charts = [this.chart1, this.chart2];
    }
    public zoomComplete(args: IZoomCompleteEventArgs): void {
        if (args.axis.name === 'primaryXAxis') {
            this.zoomFactor = args.currentZoomFactor;
            this.zoomPosition = args.currentZoomPosition;
            this.zoomCompleteFunction(args);
        }
    };
    public zoomCompleteFunction(args: any): void {
        for (let i: number = 0; i < this.charts.length; i++) {
            if (args.axis.series[0].chart.element.id !== this.charts[i].element.id) {
                this.charts[i].primaryXAxis.zoomFactor = this.zoomFactor;
                this.charts[i].primaryXAxis.zoomPosition = this.zoomPosition;
                this.charts[i].zoomModule.isZoomed = args.axis.series[0].chart.zoomModule.isZoomed;
                this.charts[i].zoomModule.isPanning = args.axis.series[0].chart.zoomModule.isPanning;
            }
        }
    };
    public selectionComplete(args: ISelectionCompleteEventArgs): void {
        this.selectionCompleteFunction(args);
    }
    public selectionCompleteFunction(args: any): void {
        if (this.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 < this.charts.length; i++) {
                if (args.chart.element.id !== this.charts[i].element.id) {
                    this.charts[i].selectedDataIndexes = args.selectedDataValues;
                    this.count += 1;
                    this.charts[i].dataBind();
                }
            }
            this.count = 0;
        }
    }

    ngOnInit(): void {
        this.chartData = synchronizedData;
        this.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
        };
        this.primaryYAxis1 = {
            labelFormat: 'n2',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 0.86,
            maximum: 0.96,
            interval: 0.025
        };
        this.primaryYAxis2 = {
            labelFormat: 'n1',
            majorTickLines: { width: 0 },
            lineStyle: { width: 0 },
            minimum: 79,
            maximum: 85,
            interval: 1.5
        };
        this.title1 = 'US to Euro';
        this.title2 = 'US to INR';
        this.zoomSettings = {
            enableSelectionZooming: true,
            mode: 'X'
        };
        this.border = {
            width: 2
        };
        this.width = 2;
        this.titleStyle = {
            textAlignment: 'Near'
        };
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));