diff --git a/api-doc/changelog.html b/api-doc/changelog.html new file mode 100644 index 00000000..e312a6af --- /dev/null +++ b/api-doc/changelog.html @@ -0,0 +1,210 @@ + + +
+ + +NgxEchartsDirective
, provideEcharts
and provideEchartsCore
chartXXX
EventEmitters. Support new events such as: 'selectchanged'
[loading]=true
is not triggered when chart initialized.resize-observer-polyfill
refreshChart()
and resize()
NgxEchartsModule
provides .forRoot()
method to inject echarts
core..forRoot
method, we can do custom build without NgxEchartsCoreModule
. Just import the echarts
core from echarts/src/echarts
, and other necessary charts.NgxEchartsCoreModule
is removed.[detectEventChanges]
is removed.+
+ projects/ngx-echarts/src/lib/change-filter.ts
+
+ Methods+ |
+
+
|
+
+constructor(changes: SimpleChanges)
+ |
+ ||||||
+ + | +||||||
+
+ Parameters :
+
+
|
+
+ + + has + + + | +||||||
+has(key: string)
+ |
+ ||||||
+ + | +||||||
+ Type parameters :
+
|
+ ||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ Observable<T>
+
+
+
+
+ |
+
+ + + notEmpty + + + | +||||||
+notEmpty(key: string)
+ |
+ ||||||
+ + | +||||||
+ Type parameters :
+
|
+ ||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ Observable<T>
+
+
+
+
+ |
+
+ + + notFirst + + + | +||||||
+notFirst(key: string)
+ |
+ ||||||
+ + | +||||||
+ Type parameters :
+
|
+ ||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ Observable<T>
+
+
+
+
+ |
+
+ + + notFirstAndEmpty + + + | +||||||
+notFirstAndEmpty(key: string)
+ |
+ ||||||
+ + | +||||||
+ Type parameters :
+
|
+ ||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ Observable<T>
+
+
+
+
+ |
+
+ + + Static + of + + + | +||||||
+
+ of(changes: SimpleChanges)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ ChangeFilter
+
+
+
+
+ |
+
import { SimpleChanges } from '@angular/core';
+import { Observable, of, EMPTY } from 'rxjs';
+
+export class ChangeFilter {
+ constructor(private changes: SimpleChanges) {}
+
+ static of(changes: SimpleChanges) {
+ return new ChangeFilter(changes);
+ }
+
+ notEmpty<T>(key: string): Observable<T> {
+ if (this.changes[key]) {
+ const value: T = this.changes[key].currentValue;
+
+ if (value !== undefined && value !== null) {
+ return of(value);
+ }
+ }
+ return EMPTY;
+ }
+
+ has<T>(key: string): Observable<T> {
+ if (this.changes[key]) {
+ const value: T = this.changes[key].currentValue;
+ return of(value);
+ }
+ return EMPTY;
+ }
+
+ notFirst<T>(key: string): Observable<T> {
+ if (this.changes[key] && !this.changes[key].isFirstChange()) {
+ const value: T = this.changes[key].currentValue;
+ return of(value);
+ }
+ return EMPTY;
+ }
+
+ notFirstAndEmpty<T>(key: string): Observable<T> {
+ if (this.changes[key] && !this.changes[key].isFirstChange()) {
+ const value: T = this.changes[key].currentValue;
+
+ if (value !== undefined && value !== null) {
+ return of(value);
+ }
+ }
+ return EMPTY;
+ }
+}
+
+ +
+ projects/ngx-echarts/src/lib/change-filter-v2.ts
+
+ Properties+ |
+
+
|
+
+ Methods+ |
+
+ + | +
+ + + Private + subject + + + | +
+ Default value : new ReplaySubject<SimpleChanges>(1)
+ |
+
+ + | +
+ + + Private + subscriptions + + + | +
+ Type : Subscription
+
+ |
+
+ Default value : new Subscription()
+ |
+
+ + | +
+ + + dispose + + + | +
+dispose()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + doFilter + + + | +||||||
+doFilter(changes: SimpleChanges)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + has + + + | +|||||||||
+has(key: string, handler: (t: T) => void)
+ |
+ |||||||||
+ + | +|||||||||
+ Type parameters :
+
|
+ |||||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + notEmpty + + + | +|||||||||
+notEmpty(key: string, handler: (t: T) => void)
+ |
+ |||||||||
+ + | +|||||||||
+ Type parameters :
+
|
+ |||||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + notFirst + + + | +|||||||||
+notFirst(key: string, handler: (t: T) => void)
+ |
+ |||||||||
+ + | +|||||||||
+ Type parameters :
+
|
+ |||||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + notFirstAndEmpty + + + | +|||||||||
+notFirstAndEmpty(key: string, handler: (t: T) => void)
+ |
+ |||||||||
+ + | +|||||||||
+ Type parameters :
+
|
+ |||||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
import { SimpleChanges } from '@angular/core';
+import { ReplaySubject, Subscription } from 'rxjs';
+
+export class ChangeFilterV2 {
+ private subject = new ReplaySubject<SimpleChanges>(1);
+ private subscriptions: Subscription = new Subscription();
+
+ doFilter(changes: SimpleChanges) {
+ this.subject.next(changes);
+ }
+
+ dispose() {
+ this.subscriptions.unsubscribe();
+ }
+
+ notEmpty<T>(key: string, handler: (t: T) => void) {
+ this.subscriptions.add(
+ this.subject.subscribe(changes => {
+ if (changes[key]) {
+ const value: T = changes[key].currentValue;
+ if (value !== undefined && value !== null) {
+ handler(value);
+ }
+ }
+ })
+ );
+ }
+
+ has<T>(key: string, handler: (t: T) => void) {
+ this.subscriptions.add(
+ this.subject.subscribe(changes => {
+ if (changes[key]) {
+ const value: T = changes[key].currentValue;
+ handler(value);
+ }
+ })
+ );
+ }
+
+ notFirst<T>(key: string, handler: (t: T) => void) {
+ this.subscriptions.add(
+ this.subject.subscribe(changes => {
+ if (changes[key] && !changes[key].isFirstChange()) {
+ const value: T = changes[key].currentValue;
+ handler(value);
+ }
+ })
+ );
+ }
+
+ notFirstAndEmpty<T>(key: string, handler: (t: T) => void) {
+ this.subscriptions.add(
+ this.subject.subscribe(changes => {
+ if (changes[key] && !changes[key].isFirstChange()) {
+ const value: T = changes[key].currentValue;
+ if (value !== undefined && value !== null) {
+ handler(value);
+ }
+ }
+ })
+ );
+ }
+}
+
+ File | +Type | +Identifier | +Statements | +
---|---|---|---|
+ + projects/ngx-echarts/src/lib/change-filter-v2.ts + | +class | +ChangeFilterV2 | ++ 0 % + (0/9) + | +
+ + projects/ngx-echarts/src/lib/change-filter.ts + | +class | +ChangeFilter | ++ 0 % + (0/7) + | +
+ + projects/ngx-echarts/src/lib/ngx-echarts.directive.ts + | +directive | +NgxEchartsDirective | ++ 2 % + (2/74) + | +
+ + projects/ngx-echarts/src/lib/ngx-echarts.directive.ts + | +interface | +NgxEchartsConfig | ++ 0 % + (0/3) + | +
+ + projects/ngx-echarts/src/lib/ngx-echarts.directive.ts + | +variable | +NGX_ECHARTS_CONFIG | ++ 0 % + (0/1) + | +
+ + projects/ngx-echarts/src/lib/ngx-echarts.module.ts + | +variable | +provideEcharts | ++ 0 % + (0/1) + | +
+ + projects/ngx-echarts/src/lib/ngx-echarts.module.ts + | +variable | +provideEchartsCore | ++ 0 % + (0/1) + | +
+
+ projects/ngx-echarts/src/lib/ngx-echarts.directive.ts
+
+
+ OnChanges
+ OnDestroy
+ OnInit
+ AfterViewInit
+
Selector | +echarts, [echarts] |
+
+constructor(config: NgxEchartsConfig, el: ElementRef, ngZone: NgZone)
+ |
+ ||||||||||||
+ + | +||||||||||||
+
+ Parameters :
+
+
|
+
+ + autoResize + | +|
+ Type : boolean
+
+ |
+ |
+ Default value : true
+ |
+ |
+ + | +
+ + initOpts + | +|
+ Type : literal type | null
+
+ |
+ |
+ Default value : null
+ |
+ |
+ + | +
+ + loading + | +|
+ Type : boolean
+
+ |
+ |
+ Default value : false
+ |
+ |
+ + | +
+ + loadingOpts + | +|
+ Type : object | null
+
+ |
+ |
+ Default value : null
+ |
+ |
+ + | +
+ + loadingType + | +|
+ Type : string
+
+ |
+ |
+ Default value : 'default'
+ |
+ |
+ + | +
+ + merge + | +|
+ Type : EChartsOption | null
+
+ |
+ |
+ Default value : null
+ |
+ |
+ + | +
+ + options + | +|
+ Type : EChartsOption | null
+
+ |
+ |
+ Default value : null
+ |
+ |
+ + | +
+ + theme + | +|
+ Type : string | ThemeOption | null
+
+ |
+ |
+ Default value : null
+ |
+ |
+ + | +
+ + chartAxisAreaSelected + | +|
+ + | +
+ + chartBrush + | +|
+ + | +
+ + chartBrushEnd + | +|
+ + | +
+ + chartBrushSelected + | +|
+ + | +
+ + chartClick + | +|
+ + | +
+ + chartContextMenu + | +|
+ + | +
+ + chartDataRangeSelected + | +|
+ + | +
+ + chartDataViewChanged + | +|
+ + | +
+ + chartDataZoom + | +|
+ + | +
+ + chartDblClick + | +|
+ + | +
+ + chartDownplay + | +|
+ + | +
+ + chartFinished + | +|
+ + | +
+ + chartGeoRoam + | +|
+ + | +
+ + chartGeoSelectChanged + | +|
+ + | +
+ + chartGeoSelected + | +|
+ + | +
+ + chartGeoUnselected + | +|
+ + | +
+ + chartGlobalCursorTaken + | +|
+ + | +
+ + chartGlobalOut + | +|
+ + | +
+ + chartGraphRoam + | +|
+ + | +
+ + chartHighlight + | +|
+ + | +
+ + chartInit + | +|
+ Type : EventEmitter
+
+ |
+ |
+ + | +
+ + chartLegendLegendInverseSelect + | +|
+ + | +
+ + chartLegendLegendSelectAll + | +|
+ + | +
+ + chartLegendScroll + | +|
+ + | +
+ + chartLegendSelectChanged + | +|
+ + | +
+ + chartLegendSelected + | +|
+ + | +
+ + chartLegendUnselected + | +|
+ + | +
+ + chartMagicTypeChanged + | +|
+ + | +
+ + chartMouseDown + | +|
+ + | +
+ + chartMouseMove + | +|
+ + | +
+ + chartMouseOut + | +|
+ + | +
+ + chartMouseOver + | +|
+ + | +
+ + chartMouseUp + | +|
+ + | +
+ + chartRendered + | +|
+ + | +
+ + chartRestore + | +|
+ + | +
+ + chartSelectChanged + | +|
+ + | +
+ + chartTimelineChanged + | +|
+ + | +
+ + chartTimelinePlayChanged + | +|
+ + | +
+ + chartTreeRoam + | +|
+ + | +
+ + optionsError + | +|
+ Type : EventEmitter
+
+ |
+ |
+ + | +
+ + + Private + createChart + + + | +
+
+ createChart()
+ |
+
+ + | +
+
+
+ Returns :
+ any
+
+ |
+
+ + + Private + createLazyEvent + + + | +||||||
+
+ createLazyEvent(eventName: string)
+ |
+ ||||||
+ + | +||||||
+ Type parameters :
+
|
+ ||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ EventEmitter<T>
+
+
+
+
+ |
+
+ + + Private + dispose + + + | +
+
+ dispose()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + Private + Async + initChart + + + | +
+
+ initChart()
+ |
+
+ + | +
+
+
+ Returns :
+ any
+
+ |
+
+ + + ngAfterViewInit + + + | +
+ngAfterViewInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + ngOnChanges + + + | +||||||
+ngOnChanges(changes: SimpleChanges)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + ngOnDestroy + + + | +
+ngOnDestroy()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + ngOnInit + + + | +
+ngOnInit()
+ |
+
+ + | +
+
+
+ Returns :
+ void
+
+ |
+
+ + + Private + Async + onOptionsChange + + + | +||||||
+
+ onOptionsChange(opt: any)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ any
+
+
+
+
+ |
+
+ + + Async + refreshChart + + + | +
+
+ refreshChart()
+ |
+
+ + | +
+ dispose old chart and create a new one. +
+ Returns :
+ any
+
+ |
+
+ + + resize + + + | +
+resize()
+ |
+
+ + | +
+ resize chart +
+ Returns :
+ void
+
+ |
+
+ + + Private + setOption + + + | +|||||||||
+
+ setOption(option: any, opts?: any)
+ |
+ |||||||||
+ + | +|||||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + Private + toggleLoading + + + | +||||||
+
+ toggleLoading(loading: boolean)
+ |
+ ||||||
+ + | +||||||
+
+
+ Parameters :
+
+
+
+
+
+ Returns :
+ void
+
+
+
+
+ |
+
+ + + Public + animationFrameID + + + | +
+ Type : null
+
+ |
+
+ Default value : null
+ |
+
+ + | +
+ + + Private + changeFilter + + + | +
+ Default value : new ChangeFilterV2()
+ |
+
+ + | +
+ + + Private + chart + + + | +
+ Type : ECharts
+
+ |
+
+ + | +
+ + + Private + chart$ + + + | +
+ Default value : new ReplaySubject<ECharts>(1)
+ |
+
+ + | +
+ + + Private + echarts + + + | +
+ Type : any
+
+ |
+
+ + | +
+ + + Private + Optional + initChartTimer + + + | +
+ Type : number
+
+ |
+
+ + | +
+ + + Private + loadingSub + + + | +
+ Type : Subscription
+
+ |
+
+ + | +
+ + + Private + resize$ + + + | +
+ Default value : new Subject<void>()
+ |
+
+ + | +
+ + + Private + resizeOb + + + | +
+ Type : ResizeObserver
+
+ |
+
+ + | +
+ + + Private + resizeObFired + + + | +
+ Type : boolean
+
+ |
+
+ Default value : false
+ |
+
+ + | +
+ + + Private + resizeSub + + + | +
+ Type : Subscription
+
+ |
+
+ + | +
import {
+ AfterViewInit,
+ Directive,
+ ElementRef,
+ EventEmitter,
+ Inject,
+ InjectionToken,
+ Input,
+ NgZone,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ Output,
+ SimpleChanges,
+} from '@angular/core';
+import { Observable, ReplaySubject, Subject, Subscription, asyncScheduler } from 'rxjs';
+import { switchMap, throttleTime } from 'rxjs/operators';
+import { ChangeFilterV2 } from './change-filter-v2';
+import type { EChartsOption, ECharts, ECElementEvent } from 'echarts';
+
+export interface NgxEchartsConfig {
+ echarts: any | (() => Promise<any>);
+ theme?: string | ThemeOption;
+}
+
+export type ThemeOption = Record<string, any>;
+
+export const NGX_ECHARTS_CONFIG = new InjectionToken<NgxEchartsConfig>('NGX_ECHARTS_CONFIG');
+
+@Directive({
+ standalone: true,
+ selector: 'echarts, [echarts]',
+ exportAs: 'echarts',
+})
+export class NgxEchartsDirective implements OnChanges, OnDestroy, OnInit, AfterViewInit {
+ @Input() options: EChartsOption | null = null;
+ @Input() theme: string | ThemeOption | null = null;
+ @Input() initOpts: {
+ devicePixelRatio?: number;
+ renderer?: string;
+ width?: number | string;
+ height?: number | string;
+ locale?: string;
+ } | null = null;
+ @Input() merge: EChartsOption | null = null;
+ @Input() autoResize = true;
+ @Input() loading = false;
+ @Input() loadingType = 'default';
+ @Input() loadingOpts: object | null = null;
+
+ // ngx-echarts events
+ @Output() chartInit = new EventEmitter<any>();
+ @Output() optionsError = new EventEmitter<Error>();
+
+ // echarts mouse events
+ @Output() chartClick = this.createLazyEvent<ECElementEvent>('click');
+ @Output() chartDblClick = this.createLazyEvent<ECElementEvent>('dblclick');
+ @Output() chartMouseDown = this.createLazyEvent<ECElementEvent>('mousedown');
+ @Output() chartMouseMove = this.createLazyEvent<ECElementEvent>('mousemove');
+ @Output() chartMouseUp = this.createLazyEvent<ECElementEvent>('mouseup');
+ @Output() chartMouseOver = this.createLazyEvent<ECElementEvent>('mouseover');
+ @Output() chartMouseOut = this.createLazyEvent<ECElementEvent>('mouseout');
+ @Output() chartGlobalOut = this.createLazyEvent<ECElementEvent>('globalout');
+ @Output() chartContextMenu = this.createLazyEvent<ECElementEvent>('contextmenu');
+
+ // echarts events
+ @Output() chartHighlight = this.createLazyEvent<any>('highlight');
+ @Output() chartDownplay = this.createLazyEvent<any>('downplay');
+ @Output() chartSelectChanged = this.createLazyEvent<any>('selectchanged');
+ @Output() chartLegendSelectChanged = this.createLazyEvent<any>('legendselectchanged');
+ @Output() chartLegendSelected = this.createLazyEvent<any>('legendselected');
+ @Output() chartLegendUnselected = this.createLazyEvent<any>('legendunselected');
+ @Output() chartLegendLegendSelectAll = this.createLazyEvent<any>('legendselectall');
+ @Output() chartLegendLegendInverseSelect = this.createLazyEvent<any>('legendinverseselect');
+ @Output() chartLegendScroll = this.createLazyEvent<any>('legendscroll');
+ @Output() chartDataZoom = this.createLazyEvent<any>('datazoom');
+ @Output() chartDataRangeSelected = this.createLazyEvent<any>('datarangeselected');
+ @Output() chartGraphRoam = this.createLazyEvent<any>('graphroam');
+ @Output() chartGeoRoam = this.createLazyEvent<any>('georoam');
+ @Output() chartTreeRoam = this.createLazyEvent<any>('treeroam');
+ @Output() chartTimelineChanged = this.createLazyEvent<any>('timelinechanged');
+ @Output() chartTimelinePlayChanged = this.createLazyEvent<any>('timelineplaychanged');
+ @Output() chartRestore = this.createLazyEvent<any>('restore');
+ @Output() chartDataViewChanged = this.createLazyEvent<any>('dataviewchanged');
+ @Output() chartMagicTypeChanged = this.createLazyEvent<any>('magictypechanged');
+ @Output() chartGeoSelectChanged = this.createLazyEvent<any>('geoselectchanged');
+ @Output() chartGeoSelected = this.createLazyEvent<any>('geoselected');
+ @Output() chartGeoUnselected = this.createLazyEvent<any>('geounselected');
+ @Output() chartAxisAreaSelected = this.createLazyEvent<any>('axisareaselected');
+ @Output() chartBrush = this.createLazyEvent<any>('brush');
+ @Output() chartBrushEnd = this.createLazyEvent<any>('brushend');
+ @Output() chartBrushSelected = this.createLazyEvent<any>('brushselected');
+ @Output() chartGlobalCursorTaken = this.createLazyEvent<any>('globalcursortaken');
+ @Output() chartRendered = this.createLazyEvent<any>('rendered');
+ @Output() chartFinished = this.createLazyEvent<any>('finished');
+
+ public animationFrameID = null;
+ private chart: ECharts;
+ private chart$ = new ReplaySubject<ECharts>(1);
+ private echarts: any;
+ private resizeOb: ResizeObserver;
+ private resize$ = new Subject<void>();
+ private resizeSub: Subscription;
+ private initChartTimer?: number;
+ private changeFilter = new ChangeFilterV2();
+ private loadingSub: Subscription;
+ private resizeObFired: boolean = false;
+
+ constructor(
+ @Inject(NGX_ECHARTS_CONFIG) config: NgxEchartsConfig,
+ private el: ElementRef,
+ private ngZone: NgZone
+ ) {
+ this.echarts = config.echarts;
+ this.theme = config.theme || null;
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ this.changeFilter.doFilter(changes);
+ }
+
+ ngOnInit() {
+ if (!window.ResizeObserver) {
+ throw new Error('please install a polyfill for ResizeObserver');
+ }
+ this.resizeSub = this.resize$
+ .pipe(throttleTime(100, asyncScheduler, { leading: false, trailing: true }))
+ .subscribe(() => this.resize());
+
+ if (this.autoResize) {
+ // https://github.com/xieziyu/ngx-echarts/issues/413
+ this.resizeOb = this.ngZone.runOutsideAngular(
+ () =>
+ new window.ResizeObserver(entries => {
+ for (const entry of entries) {
+ if (entry.target === this.el.nativeElement) {
+ // Ignore first fire on insertion, no resize actually happened
+ if (!this.resizeObFired) {
+ this.resizeObFired = true;
+ } else {
+ this.animationFrameID = window.requestAnimationFrame(() => {
+ this.resize$.next();
+ });
+ }
+ }
+ }
+ })
+ );
+ this.resizeOb.observe(this.el.nativeElement);
+ }
+
+ this.changeFilter.notFirstAndEmpty('options', opt => this.onOptionsChange(opt));
+ this.changeFilter.notFirstAndEmpty('merge', opt => this.setOption(opt));
+ this.changeFilter.has<boolean>('loading', v => this.toggleLoading(!!v));
+ this.changeFilter.notFirst<string | ThemeOption>('theme', () => this.refreshChart());
+ }
+
+ ngOnDestroy() {
+ window.clearTimeout(this.initChartTimer);
+ if (this.resizeSub) {
+ this.resizeSub.unsubscribe();
+ }
+ if (this.animationFrameID) {
+ window.cancelAnimationFrame(this.animationFrameID);
+ }
+ if (this.resizeOb) {
+ this.resizeOb.unobserve(this.el.nativeElement);
+ }
+ if (this.loadingSub) {
+ this.loadingSub.unsubscribe();
+ }
+ this.changeFilter.dispose();
+ this.dispose();
+ }
+
+ ngAfterViewInit() {
+ this.initChartTimer = window.setTimeout(() => this.initChart());
+ }
+
+ private dispose() {
+ if (this.chart) {
+ if (!this.chart.isDisposed()) {
+ this.chart.dispose();
+ }
+ this.chart = null;
+ }
+ }
+
+ /**
+ * resize chart
+ */
+ resize() {
+ if (this.chart) {
+ this.chart.resize();
+ }
+ }
+
+ private toggleLoading(loading: boolean) {
+ if (this.chart) {
+ loading
+ ? this.chart.showLoading(this.loadingType, this.loadingOpts)
+ : this.chart.hideLoading();
+ } else {
+ this.loadingSub = this.chart$.subscribe(chart =>
+ loading ? chart.showLoading(this.loadingType, this.loadingOpts) : chart.hideLoading()
+ );
+ }
+ }
+
+ private setOption(option: any, opts?: any) {
+ if (this.chart) {
+ try {
+ this.chart.setOption(option, opts);
+ } catch (e) {
+ console.error(e);
+ this.optionsError.emit(e);
+ }
+ }
+ }
+
+ /**
+ * dispose old chart and create a new one.
+ */
+ async refreshChart() {
+ this.dispose();
+ await this.initChart();
+ }
+
+ private createChart() {
+ const dom = this.el.nativeElement;
+
+ if (window && window.getComputedStyle) {
+ const prop = window.getComputedStyle(dom, null).getPropertyValue('height');
+ if ((!prop || prop === '0px') && (!dom.style.height || dom.style.height === '0px')) {
+ dom.style.height = '400px';
+ }
+ }
+
+ // here a bit tricky: we check if the echarts module is provided as function returning native import('...') then use the promise
+ // otherwise create the function that imitates behaviour above with a provided as is module
+ return this.ngZone.runOutsideAngular(() => {
+ const load =
+ typeof this.echarts === 'function' ? this.echarts : () => Promise.resolve(this.echarts);
+
+ return load().then(({ init }) => init(dom, this.theme, this.initOpts));
+ });
+ }
+
+ private async initChart() {
+ await this.onOptionsChange(this.options);
+
+ if (this.merge && this.chart) {
+ this.setOption(this.merge);
+ }
+ }
+
+ private async onOptionsChange(opt: any) {
+ if (!opt) {
+ return;
+ }
+
+ if (this.chart) {
+ this.setOption(this.options, true);
+ } else {
+ this.chart = await this.createChart();
+ this.chart$.next(this.chart);
+ this.chartInit.emit(this.chart);
+ this.setOption(this.options, true);
+ }
+ }
+
+ // allows to lazily bind to only those events that are requested through the `@Output` by parent components
+ // see https://stackoverflow.com/questions/51787972/optimal-reentering-the-ngzone-from-eventemitter-event for more info
+ private createLazyEvent<T>(eventName: string): EventEmitter<T> {
+ return this.chartInit.pipe(
+ switchMap(
+ (chart: any) =>
+ new Observable(observer => {
+ chart.on(eventName, (data: T) => this.ngZone.run(() => observer.next(data)));
+ return () => {
+ if (this.chart) {
+ if (!this.chart.isDisposed()) {
+ chart.off(eventName);
+ }
+ }
+ };
+ })
+ )
+ ) as EventEmitter<T>;
+ }
+}
+
+ ngx-echarts
is an Angular (ver >= 2.x) directive for ECharts (ver >= 3.x).
Latest version @npm:
+v18.0.0
for Angular 18v17.2.0
for Angular 17v16.2.0
for Angular 16v15.0.3
for Angular 15v14.0.0
for Angular 14v8.0.1
for Angular 13v7.1.0
for Angular >= 11, < 13v6.0.1
for Angular >= 10, < 11v5.2.2
for Angular >= 6, < 10v2.3.1
for Angular < 6 (Please refer to https://github.com/xieziyu/ngx-echarts/blob/v2.x/README.md)A starter project on Github: https://github.com/xieziyu/ngx-echarts-starter
+Since v5.0
+# if you use npm
+npm install echarts -S
+npm install ngx-echarts -S
+
+# or if you use yarn
+yarn add echarts
+yarn add ngx-echarts
If you need ECharts GL support, please install it first:
+npm install echarts-gl -S
+
+# or
+yarn add echarts-gl
Import other extensions such as themes or echarts-gl
in your main.ts
: ECharts Extensions
echarts
and provide it in NgxEchartsModule.forRoot({ echarts })
.NgxEchartsCoreModule
is removed.Please refer to the demo page.
+import NgxEchartsDirective
and provideEcharts
. Or you can use provideEchartsCore
to do treeshaking custom build.
import { NgxEchartsDirective, provideEcharts } from 'ngx-echarts';
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [CommonModule, NgxEchartsDirective],
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss'],
+ providers: [
+ provideEcharts(),
+ ]
+})
+export class AppComponent {}
import NgxEchartsModule
:
import { NgxEchartsModule } from 'ngx-echarts';
+
+@NgModule({
+ imports: [
+ NgxEchartsModule.forRoot({
+ /**
+ * This will import all modules from echarts.
+ * If you only need custom modules,
+ * please refer to [Custom Build] section.
+ */
+ echarts: () => import('echarts'), // or import('./path-to-my-custom-echarts')
+ }),
+ ],
+})
+export class AppModule {}
The echarts library will be imported only when it gets called the first time thanks to the function that uses the native import.
+You can also directly pass the echarts instead which will slow down initial rendering because it will load the whole echarts into your main bundle.
+import * as echarts from 'echarts';
+import { NgxEchartsModule } from 'ngx-echarts';
+
+@NgModule({
+ imports: [
+ NgxEchartsModule.forRoot({ echarts }),
+ ],
+})
+export class AppModule {}
use echarts
directive in a div which has pre-defined height. (From v2.0, it has default height: 400px)
<div echarts [options]="chartOption" class="demo-chart"></div>
.demo-chart {
+ height: 400px;
+}
import { EChartsOption } from 'echarts';
+
+// ...
+
+chartOption: EChartsOption = {
+ xAxis: {
+ type: 'category',
+ data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+ },
+ yAxis: {
+ type: 'value',
+ },
+ series: [
+ {
+ data: [820, 932, 901, 934, 1290, 1330, 1320],
+ type: 'line',
+ },
+ ],
+};
echarts
directive support following input properties:
Input | +Type | +Default | +Description | +
---|---|---|---|
[options] |
+object | +null | +The same as the options on the official demo site. | +
[merge] |
+object | +null | +Used to update a part of the options , especially helpful when you need to update the chart data. In fact, the value of merge will be used in echartsInstance.setOption() with notMerge = false . Refer to ECharts documentation for details. |
+
[loading] |
+boolean | +false | +Used to toggle the echarts loading animation when your data is not ready. | +
[autoResize] |
+boolean | +true | +If set to true , the chart will be automatically resized when the window's width is changed. |
+
[initOpts] |
+object | +null | +The value of [initOpts] will be used in echarts.init() . It may contain devicePixelRatio , renderer , width or height properties. Refer to ECharts documentation for details. |
+
[theme] |
+string | +null | +Used it to initialize echarts with theme. The theme file must also be imported in main.ts . |
+
[loadingOpts] |
+object | +null | +Input an object to customize the loading style. Refer to ECharts documentation for details. | +
By default, loadingOpts
is:
{
+ text: 'loading',
+ color: '#c23531',
+ textColor: '#000',
+ maskColor: 'rgba(255, 255, 255, 0.8)',
+ zlevel: 0
+}
If you need to access parts of the ECharts API such as echarts.graphic
, please import it from echarts. For example:
import { graphic } from 'echarts';
+
+new graphic.LinearGradient(/* ... */);
echartsInstance
is exposed (since v1.1.6) in the (chartInit)
event, enabling you to directly call functions like: resize()
, showLoading()
, etc. For example:
<div echarts class="demo-chart" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
onChartInit(ec) {
+ this.echartsInstance = ec;
+}
+
+resizeChart() {
+ if (this.echartsInstance) {
+ this.echartsInstance.resize();
+ }
+}
Import echarts theme files or other extension files after you have imported echarts
core. For example:
import * as echarts from 'echarts';
+
+/** echarts extensions: */
+import 'echarts-gl';
+import 'echarts/theme/macarons.js';
+import 'echarts/dist/extension/bmap.min.js';
NgxEchartsService
has been obsolete since v4.0
As ECharts supports the 'click'
, 'dblclick'
, 'mousedown'
, 'mouseup'
, 'mouseover'
, 'mouseout'
, and 'globalout'
mouse events, our ngx-echarts
directive also supports the same mouse events but with an additional chart
prefix. For example:
<div echarts class="demo-chart" [options]="chartOptions" (chartClick)="onChartClick($event)"></div>
It supports following event outputs:
+@Output | +Event | +
---|---|
chartInit | +Emitted when the chart is initialized | +
chartClick | +echarts event: 'click' |
+
chartDblClick | +echarts event: 'dblclick' |
+
chartMouseDown | +echarts event: 'mousedown' |
+
chartMouseMove | +echarts event: 'mousemove' |
+
chartMouseUp | +echarts event: 'mouseup' |
+
chartMouseOver | +echarts event: 'mouseover' |
+
chartMouseOut | +echarts event: 'mouseout' |
+
chartGlobalOut | +echarts event: 'globalout' |
+
chartContextMenu | +echarts event: 'contextmenu' |
+
chartHighlight | +echarts event: 'highlight' |
+
chartDownplay | +echarts event: 'downplay' |
+
chartSelectChanged | +echarts event: 'selectchanged' |
+
chartLegendSelectChanged | +echarts event: 'legendselectchanged' |
+
chartLegendSelected | +echarts event: 'legendselected' |
+
chartLegendUnselected | +echarts event: 'legendunselected' |
+
chartLegendLegendSelectAll | +echarts event: 'legendselectall' |
+
chartLegendLegendInverseSelect | +echarts event: 'legendinverseselect' |
+
chartLegendScroll | +echarts event: 'legendscroll' |
+
chartDataZoom | +echarts event: 'datazoom' |
+
chartDataRangeSelected | +echarts event: 'datarangeselected' |
+
chartGraphRoam | +echarts event: 'graphroam' |
+
chartGeoRoam | +echarts event: 'georoam' |
+
chartTreeRoam | +echarts event: 'treeroam' |
+
chartTimelineChanged | +echarts event: 'timelinechanged' |
+
chartTimelinePlayChanged | +echarts event: 'timelineplaychanged' |
+
chartRestore | +echarts event: 'restore' |
+
chartDataViewChanged | +echarts event: 'dataviewchanged' |
+
chartMagicTypeChanged | +echarts event: 'magictypechanged' |
+
chartGeoSelectChanged | +echarts event: 'geoselectchanged' |
+
chartGeoSelected | +echarts event: 'geoselected' |
+
chartGeoUnselected | +echarts event: 'geounselected' |
+
chartAxisAreaSelected | +echarts event: 'axisareaselected' |
+
chartBrush | +echarts event: 'brush' |
+
chartBrushEnd | +echarts event: 'brushend' |
+
chartBrushSelected | +echarts event: 'brushselected' |
+
chartGlobalCursorTaken | +echarts event: 'globalcursortaken' |
+
chartRendered | +echarts event: 'rendered' |
+
chartFinished | +echarts event: 'finished' |
+
You can refer to the ECharts tutorial: Events and Actions in ECharts for more details of the event params. You can also refer to the demo page for a detailed example.
+++Since version 5.0.1 ECharts supports Treeshaking with NPM.
+
The app.modules.ts
should look like this:
import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+import { HttpClientModule } from '@angular/common/http';
+
+import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts';
+
+import { AppComponent } from './app.component';
+
+// Import the echarts core module, which provides the necessary interfaces for using echarts.
+import * as echarts from 'echarts/core';
+
+// Import bar charts, all suffixed with Chart
+import { BarChart } from 'echarts/charts';
+
+// Import the tooltip, title, rectangular coordinate system, dataset and transform components
+import {
+ TitleComponent,
+ TooltipComponent,
+ GridComponent,
+ DatasetComponent,
+ TransformComponent
+} from 'echarts/components';
+
+// Features like Universal Transition and Label Layout
+import { LabelLayout, UniversalTransition } from 'echarts/features';
+
+// Import the Canvas renderer
+// Note that including the CanvasRenderer or SVGRenderer is a required step
+import { CanvasRenderer } from 'echarts/renderers';
+
+// Import the theme
+import 'echarts/theme/macarons.js';
+
+// Register the required components
+echarts.use([
+ BarChart,
+ TitleComponent,
+ TooltipComponent,
+ GridComponent,
+ DatasetComponent,
+ TransformComponent,
+ LabelLayout,
+ UniversalTransition,
+ CanvasRenderer
+]);
+
+@NgModule({
+ declarations: [AppComponent],
+ imports: [
+ BrowserModule,
+ HttpClientModule,
+ // import standalone directive:
+ NgxEchartsDirective,
+ ],
+ providers: [{
+ // Provide custom builded ECharts core:
+ provideEchartsCore({ echarts })
+ }],
+ bootstrap: [AppComponent],
+})
+export class AppModule {}
++Please refer to ECharts Documentation for more details.
+
If you want to produce a custom build of ECharts, prepare a file like custom-echarts.ts
:
// custom-echarts.ts
+export * from 'echarts/src/echarts';
+
+import 'echarts/src/chart/line';
+import 'echarts/src/chart/bar';
+// component examples:
+import 'echarts/src/component/tooltip';
+import 'echarts/src/component/title';
+import 'echarts/src/component/toolbox';
And then inject it in your NgxEchartsModule
:
import { NgxEchartsModule } from 'ngx-echarts';
+import * as echarts from './custom-echarts';
+
+@NgModule({
+ imports: [
+ NgxEchartsModule.forRoot({
+ echarts,
+ }),
+ ],
+})
+export class AppModule {}
And if you want to use the global echarts
object, please import it from lib
or src
instead:
import * as echarts from 'echarts/lib/echarts';
If you need to import theme files, remember to change the 'echarts'
path to 'echarts/lib/echarts'
, for example:
// ... part of echarts/theme/dark.js:
+function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['exports', 'echarts/lib/echarts'], factory);
+ } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
+ // CommonJS
+ factory(exports, require('echarts/lib/echarts'));
+ } else {
+ // Browser globals
+ factory({}, root.echarts);
+ }
+}
You can change the chart locale registering a built-in locale (located in node_modules/echarts/lib/i18n/
) or a custom locale object. To register a locale, you will need to change the module that echart is being imported (usually app.module.ts
).
import {NgxEchartsModule} from "ngx-echarts";
+import * as echarts from 'echarts/core';
+import langCZ from 'echarts/lib/i18n/langCZ';
+
+echarts.registerLocale("CZ", langCZ)
+
+@NgModule({
+ imports: [NgxEchartsModule.forRoot({echarts})],
+ declarations: [],
+ providers: [],
+ bootstrap: [AppComponent]
+})
and in your HTML file use:
+<div echarts [initOpts]="{ locale: 'CZ' }" [options]="options" class="demo-chart"></div>
You can clone this repo to your working copy and then launch the demo page in your local machine:
+npm install
+npm run demo
+
+# or
+yarn install
+yarn demo
The demo page server is listening on: http://localhost:4202
+ + + + + + + + + + + + + + + + + + + + + ++
+ projects/ngx-echarts/src/lib/ngx-echarts.directive.ts
+
+ Properties+ |
+
+
|
+
+ + echarts + + + + + | +
+ echarts:
+ |
+
+ Type : any |
+
+ |
+
+ + theme + + + + + | +
+ theme:
+ |
+
+ Type : string | ThemeOption
+
+ |
+
+ Optional + | +
import {
+ AfterViewInit,
+ Directive,
+ ElementRef,
+ EventEmitter,
+ Inject,
+ InjectionToken,
+ Input,
+ NgZone,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ Output,
+ SimpleChanges,
+} from '@angular/core';
+import { Observable, ReplaySubject, Subject, Subscription, asyncScheduler } from 'rxjs';
+import { switchMap, throttleTime } from 'rxjs/operators';
+import { ChangeFilterV2 } from './change-filter-v2';
+import type { EChartsOption, ECharts, ECElementEvent } from 'echarts';
+
+export interface NgxEchartsConfig {
+ echarts: any | (() => Promise<any>);
+ theme?: string | ThemeOption;
+}
+
+export type ThemeOption = Record<string, any>;
+
+export const NGX_ECHARTS_CONFIG = new InjectionToken<NgxEchartsConfig>('NGX_ECHARTS_CONFIG');
+
+@Directive({
+ standalone: true,
+ selector: 'echarts, [echarts]',
+ exportAs: 'echarts',
+})
+export class NgxEchartsDirective implements OnChanges, OnDestroy, OnInit, AfterViewInit {
+ @Input() options: EChartsOption | null = null;
+ @Input() theme: string | ThemeOption | null = null;
+ @Input() initOpts: {
+ devicePixelRatio?: number;
+ renderer?: string;
+ width?: number | string;
+ height?: number | string;
+ locale?: string;
+ } | null = null;
+ @Input() merge: EChartsOption | null = null;
+ @Input() autoResize = true;
+ @Input() loading = false;
+ @Input() loadingType = 'default';
+ @Input() loadingOpts: object | null = null;
+
+ // ngx-echarts events
+ @Output() chartInit = new EventEmitter<any>();
+ @Output() optionsError = new EventEmitter<Error>();
+
+ // echarts mouse events
+ @Output() chartClick = this.createLazyEvent<ECElementEvent>('click');
+ @Output() chartDblClick = this.createLazyEvent<ECElementEvent>('dblclick');
+ @Output() chartMouseDown = this.createLazyEvent<ECElementEvent>('mousedown');
+ @Output() chartMouseMove = this.createLazyEvent<ECElementEvent>('mousemove');
+ @Output() chartMouseUp = this.createLazyEvent<ECElementEvent>('mouseup');
+ @Output() chartMouseOver = this.createLazyEvent<ECElementEvent>('mouseover');
+ @Output() chartMouseOut = this.createLazyEvent<ECElementEvent>('mouseout');
+ @Output() chartGlobalOut = this.createLazyEvent<ECElementEvent>('globalout');
+ @Output() chartContextMenu = this.createLazyEvent<ECElementEvent>('contextmenu');
+
+ // echarts events
+ @Output() chartHighlight = this.createLazyEvent<any>('highlight');
+ @Output() chartDownplay = this.createLazyEvent<any>('downplay');
+ @Output() chartSelectChanged = this.createLazyEvent<any>('selectchanged');
+ @Output() chartLegendSelectChanged = this.createLazyEvent<any>('legendselectchanged');
+ @Output() chartLegendSelected = this.createLazyEvent<any>('legendselected');
+ @Output() chartLegendUnselected = this.createLazyEvent<any>('legendunselected');
+ @Output() chartLegendLegendSelectAll = this.createLazyEvent<any>('legendselectall');
+ @Output() chartLegendLegendInverseSelect = this.createLazyEvent<any>('legendinverseselect');
+ @Output() chartLegendScroll = this.createLazyEvent<any>('legendscroll');
+ @Output() chartDataZoom = this.createLazyEvent<any>('datazoom');
+ @Output() chartDataRangeSelected = this.createLazyEvent<any>('datarangeselected');
+ @Output() chartGraphRoam = this.createLazyEvent<any>('graphroam');
+ @Output() chartGeoRoam = this.createLazyEvent<any>('georoam');
+ @Output() chartTreeRoam = this.createLazyEvent<any>('treeroam');
+ @Output() chartTimelineChanged = this.createLazyEvent<any>('timelinechanged');
+ @Output() chartTimelinePlayChanged = this.createLazyEvent<any>('timelineplaychanged');
+ @Output() chartRestore = this.createLazyEvent<any>('restore');
+ @Output() chartDataViewChanged = this.createLazyEvent<any>('dataviewchanged');
+ @Output() chartMagicTypeChanged = this.createLazyEvent<any>('magictypechanged');
+ @Output() chartGeoSelectChanged = this.createLazyEvent<any>('geoselectchanged');
+ @Output() chartGeoSelected = this.createLazyEvent<any>('geoselected');
+ @Output() chartGeoUnselected = this.createLazyEvent<any>('geounselected');
+ @Output() chartAxisAreaSelected = this.createLazyEvent<any>('axisareaselected');
+ @Output() chartBrush = this.createLazyEvent<any>('brush');
+ @Output() chartBrushEnd = this.createLazyEvent<any>('brushend');
+ @Output() chartBrushSelected = this.createLazyEvent<any>('brushselected');
+ @Output() chartGlobalCursorTaken = this.createLazyEvent<any>('globalcursortaken');
+ @Output() chartRendered = this.createLazyEvent<any>('rendered');
+ @Output() chartFinished = this.createLazyEvent<any>('finished');
+
+ public animationFrameID = null;
+ private chart: ECharts;
+ private chart$ = new ReplaySubject<ECharts>(1);
+ private echarts: any;
+ private resizeOb: ResizeObserver;
+ private resize$ = new Subject<void>();
+ private resizeSub: Subscription;
+ private initChartTimer?: number;
+ private changeFilter = new ChangeFilterV2();
+ private loadingSub: Subscription;
+ private resizeObFired: boolean = false;
+
+ constructor(
+ @Inject(NGX_ECHARTS_CONFIG) config: NgxEchartsConfig,
+ private el: ElementRef,
+ private ngZone: NgZone
+ ) {
+ this.echarts = config.echarts;
+ this.theme = config.theme || null;
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ this.changeFilter.doFilter(changes);
+ }
+
+ ngOnInit() {
+ if (!window.ResizeObserver) {
+ throw new Error('please install a polyfill for ResizeObserver');
+ }
+ this.resizeSub = this.resize$
+ .pipe(throttleTime(100, asyncScheduler, { leading: false, trailing: true }))
+ .subscribe(() => this.resize());
+
+ if (this.autoResize) {
+ // https://github.com/xieziyu/ngx-echarts/issues/413
+ this.resizeOb = this.ngZone.runOutsideAngular(
+ () =>
+ new window.ResizeObserver(entries => {
+ for (const entry of entries) {
+ if (entry.target === this.el.nativeElement) {
+ // Ignore first fire on insertion, no resize actually happened
+ if (!this.resizeObFired) {
+ this.resizeObFired = true;
+ } else {
+ this.animationFrameID = window.requestAnimationFrame(() => {
+ this.resize$.next();
+ });
+ }
+ }
+ }
+ })
+ );
+ this.resizeOb.observe(this.el.nativeElement);
+ }
+
+ this.changeFilter.notFirstAndEmpty('options', opt => this.onOptionsChange(opt));
+ this.changeFilter.notFirstAndEmpty('merge', opt => this.setOption(opt));
+ this.changeFilter.has<boolean>('loading', v => this.toggleLoading(!!v));
+ this.changeFilter.notFirst<string | ThemeOption>('theme', () => this.refreshChart());
+ }
+
+ ngOnDestroy() {
+ window.clearTimeout(this.initChartTimer);
+ if (this.resizeSub) {
+ this.resizeSub.unsubscribe();
+ }
+ if (this.animationFrameID) {
+ window.cancelAnimationFrame(this.animationFrameID);
+ }
+ if (this.resizeOb) {
+ this.resizeOb.unobserve(this.el.nativeElement);
+ }
+ if (this.loadingSub) {
+ this.loadingSub.unsubscribe();
+ }
+ this.changeFilter.dispose();
+ this.dispose();
+ }
+
+ ngAfterViewInit() {
+ this.initChartTimer = window.setTimeout(() => this.initChart());
+ }
+
+ private dispose() {
+ if (this.chart) {
+ if (!this.chart.isDisposed()) {
+ this.chart.dispose();
+ }
+ this.chart = null;
+ }
+ }
+
+ /**
+ * resize chart
+ */
+ resize() {
+ if (this.chart) {
+ this.chart.resize();
+ }
+ }
+
+ private toggleLoading(loading: boolean) {
+ if (this.chart) {
+ loading
+ ? this.chart.showLoading(this.loadingType, this.loadingOpts)
+ : this.chart.hideLoading();
+ } else {
+ this.loadingSub = this.chart$.subscribe(chart =>
+ loading ? chart.showLoading(this.loadingType, this.loadingOpts) : chart.hideLoading()
+ );
+ }
+ }
+
+ private setOption(option: any, opts?: any) {
+ if (this.chart) {
+ try {
+ this.chart.setOption(option, opts);
+ } catch (e) {
+ console.error(e);
+ this.optionsError.emit(e);
+ }
+ }
+ }
+
+ /**
+ * dispose old chart and create a new one.
+ */
+ async refreshChart() {
+ this.dispose();
+ await this.initChart();
+ }
+
+ private createChart() {
+ const dom = this.el.nativeElement;
+
+ if (window && window.getComputedStyle) {
+ const prop = window.getComputedStyle(dom, null).getPropertyValue('height');
+ if ((!prop || prop === '0px') && (!dom.style.height || dom.style.height === '0px')) {
+ dom.style.height = '400px';
+ }
+ }
+
+ // here a bit tricky: we check if the echarts module is provided as function returning native import('...') then use the promise
+ // otherwise create the function that imitates behaviour above with a provided as is module
+ return this.ngZone.runOutsideAngular(() => {
+ const load =
+ typeof this.echarts === 'function' ? this.echarts : () => Promise.resolve(this.echarts);
+
+ return load().then(({ init }) => init(dom, this.theme, this.initOpts));
+ });
+ }
+
+ private async initChart() {
+ await this.onOptionsChange(this.options);
+
+ if (this.merge && this.chart) {
+ this.setOption(this.merge);
+ }
+ }
+
+ private async onOptionsChange(opt: any) {
+ if (!opt) {
+ return;
+ }
+
+ if (this.chart) {
+ this.setOption(this.options, true);
+ } else {
+ this.chart = await this.createChart();
+ this.chart$.next(this.chart);
+ this.chartInit.emit(this.chart);
+ this.setOption(this.options, true);
+ }
+ }
+
+ // allows to lazily bind to only those events that are requested through the `@Output` by parent components
+ // see https://stackoverflow.com/questions/51787972/optimal-reentering-the-ngzone-from-eventemitter-event for more info
+ private createLazyEvent<T>(eventName: string): EventEmitter<T> {
+ return this.chartInit.pipe(
+ switchMap(
+ (chart: any) =>
+ new Observable(observer => {
+ chart.on(eventName, (data: T) => this.ngZone.run(() => observer.next(data)));
+ return () => {
+ if (this.chart) {
+ if (!this.chart.isDisposed()) {
+ chart.off(eventName);
+ }
+ }
+ };
+ })
+ )
+ ) as EventEmitter<T>;
+ }
+}
+
+