Skip to content

Commit

Permalink
feat(chart:echarts): add chart-echarts component (#1238)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk committed Apr 22, 2021
1 parent 393f0a3 commit ce04327
Show file tree
Hide file tree
Showing 37 changed files with 513 additions and 22 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -62,6 +62,7 @@
"@angular/elements": "~11.2.1",
"@antv/data-set": "^0.11.8",
"@antv/g2": "^4.1.14",
"echarts": "^5.1.0",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"@stackblitz/sdk": "^1.5.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/chart/bar/index.md
Expand Up @@ -2,7 +2,7 @@
title: g2-bar
subtitle: 柱状图
cols: 1
type: Components
type: G2
module: import { G2BarModule } from '@delon/chart/bar';
---

Expand Down
2 changes: 1 addition & 1 deletion packages/chart/card/index.md
Expand Up @@ -2,7 +2,7 @@
title: g2-card
subtitle: 图表卡片
cols: 2
type: Components
type: G2
module: import { G2CardModule } from '@delon/chart/card';
---

Expand Down
80 changes: 80 additions & 0 deletions packages/chart/chart-echarts/demo/basic.md
@@ -0,0 +1,80 @@
---
order: 0
title:
zh-CN: 基本
en-US: Basic
---

## zh-CN

最简单的用法。

## en-US

Simplest of usage.

```ts
import { Component } from '@angular/core';
import { ChartEChartsEvent, ChartEChartsOption } from '@delon/chart/chart-echarts';

@Component({
selector: 'app-demo',
template: `
<div class="mb-md">
<nz-switch [(ngModel)]="dark"></nz-switch> Dark
<button nz-button (click)="two = !two" nzType="primary">Change Option</button>
</div>
<chart-echarts [option]="two ? option1 : option2" [theme]="dark ? 'dark' : null" (events)="handleEvents($event)"></chart-echarts>
`,
})
export class DemoComponent {
dark = false;
two = false;

option1: ChartEChartsOption = {
tooltip: {
formatter: '{a} <br/>{b} : {c}%',
},
series: [
{
name: 'Pressure',
type: 'gauge',
detail: {
formatter: '{value}',
},
data: [
{
value: 50,
name: 'SCORE',
},
],
},
],
};

option2: ChartEChartsOption = {
title: {
text: 'ECharts 入门示例',
},
tooltip: {},
legend: {
data: ['销量'],
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20],
},
],
};

handleEvents(ev: ChartEChartsEvent): void {
console.log(ev);
}
}
```
148 changes: 148 additions & 0 deletions packages/chart/chart-echarts/echarts.component.ts
@@ -0,0 +1,148 @@
import { Platform } from '@angular/cdk/platform';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
NgZone,
OnDestroy,
OnInit,
Output,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { InputNumber, NumberInput, ZoneOutside } from '@delon/util/decorator';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { ChartEChartsService } from './echarts.service';
import { ChartECharts, ChartEChartsEvent, ChartEChartsEventType, ChartEChartsOption } from './echarts.types';

@Component({
selector: 'chart-echarts, [chart-echarts]',
exportAs: 'chartECharts',
template: `
<nz-skeleton *ngIf="!loaded"></nz-skeleton>
<div #container [style.width.px]="width" [style.height.px]="height"></div>
`,
host: {
'[style.display]': `'inline-block'`,
},
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class ChartEChartsComponent implements OnInit, OnDestroy {
static ngAcceptInputType_width: NumberInput;
static ngAcceptInputType_height: NumberInput;

@ViewChild('container', { static: true }) private node: ElementRef;
private destroy$ = new Subject<void>();
private _chart: ChartECharts;
private _theme?: string | object | null;
private _initOpt?: {
renderer?: any;
devicePixelRatio?: number;
width?: number;
height?: number;
locale?: any;
};
private _option: ChartEChartsOption;

@Input() @InputNumber() width = 600;
@Input() @InputNumber() height = 400;
@Input()
set theme(value: string | object | null | undefined) {
this._theme = value;
if (this._chart) {
this.install();
}
}
@Input()
set initOpt(value: any) {
this._initOpt = value;
if (this._chart) {
this.install();
}
}
@Input()
set option(value: ChartEChartsOption) {
this._option = value;
if (this._chart) {
this.setOption(value, true);
}
}
@Output() events = new EventEmitter<ChartEChartsEvent>();

get chart(): ChartECharts {
return this._chart;
}
loaded = false;

constructor(private srv: ChartEChartsService, private cdr: ChangeDetectorRef, private ngZone: NgZone, private platform: Platform) {
this.srv.notify
.pipe(
takeUntil(this.destroy$),
filter(() => !this.loaded),
)
.subscribe(() => this.load());

this.theme = srv.cog.echartsTheme;
}

private emit(type: ChartEChartsEventType, other?: ChartEChartsEvent): void {
this.events.emit({ type, chart: this.chart, ...other });
}

@ZoneOutside()
private load(): void {
this.ngZone.run(() => {
this.loaded = true;
this.cdr.detectChanges();
});
this.emit('ready');
this.install();
}

install(): this {
this.destroy();
this._chart = (window as any).echarts.init(this.node.nativeElement, this._theme, this._initOpt);
this.emit('init');
this.setOption(this._option!);
return this;
}

destroy(): this {
if (this._chart) {
this._chart.dispose();
this.emit('destroy');
}
return this;
}

setOption(option: ChartEChartsOption, notMerge: boolean = false, lazyUpdate: boolean = false): this {
if (this._chart) {
this._chart.setOption(option, notMerge, lazyUpdate);
this.emit('set-option', { option } as any);
}
return this;
}

ngOnInit(): void {
if (!this.platform.isBrowser) {
return;
}
if ((window as any).echarts) {
this.load();
} else {
this.srv.libLoad();
}
}

ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
this.destroy();
}
}
13 changes: 13 additions & 0 deletions packages/chart/chart-echarts/echarts.module.ts
@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { NzSkeletonModule } from 'ng-zorro-antd/skeleton';
import { ChartEChartsComponent } from './echarts.component';

const COMPONENTS = [ChartEChartsComponent];

@NgModule({
imports: [CommonModule, NzSkeletonModule],
declarations: COMPONENTS,
exports: COMPONENTS,
})
export class ChartEChartsModule {}
62 changes: 62 additions & 0 deletions packages/chart/chart-echarts/echarts.service.ts
@@ -0,0 +1,62 @@
import { Injectable, OnDestroy } from '@angular/core';
import { AlainChartConfig, AlainConfigService } from '@delon/util/config';
import { LazyService } from '@delon/util/other';
import { Observable, Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class ChartEChartsService implements OnDestroy {
private _cog: AlainChartConfig;
private loading = false;
private loaded = false;
private notify$ = new Subject<void>();

get cog(): AlainChartConfig {
return this._cog;
}
set cog(val: AlainChartConfig) {
this._cog = this.cogSrv.merge(
'chart',
{
theme: '',
echartsLib: 'https://cdnjs.cloudflare.com/ajax/libs/echarts/5.1.0/echarts.min.js',
} as AlainChartConfig,
val,
)!;
}

constructor(private cogSrv: AlainConfigService, private lazySrv: LazyService) {
this.cog = { theme: '' };
}

libLoad(): this {
if (this.loading) {
if (this.loaded) {
this.notify$.next();
}
return this;
}
this.loading = true;
this.lazySrv
.load(this.cog.echartsLib!)
.then(() => {
const extensions = this.cog.echartsExtensions;
if (Array.isArray(extensions) && extensions.length > 0) {
return this.lazySrv.load(extensions).then(() => true);
}
return Promise.resolve(true);
})
.then(() => {
this.loaded = true;
this.notify$.next();
});
return this;
}

get notify(): Observable<void> {
return this.notify$.asObservable();
}

ngOnDestroy(): void {
this.notify$.unsubscribe();
}
}

0 comments on commit ce04327

Please sign in to comment.