From 0f9839bf6e2379b4a607c85168a5d2e5708875f4 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:38:38 +0800 Subject: [PATCH] [Cherry-pick]Switch to a new chart library (#19263) Switch to a new chart library Signed-off-by: AllForNothing --- src/portal/package-lock.json | 34 +++++-- src/portal/package.json | 2 +- .../single-bar/single-bar.component.ts | 80 +++++++++------- .../vulnerability-summary.component.html | 2 +- .../vulnerability-summary.component.ts | 93 ++++++++++--------- src/portal/src/app/shared/shared.module.ts | 28 +++++- 6 files changed, 151 insertions(+), 88 deletions(-) diff --git a/src/portal/package-lock.json b/src/portal/package-lock.json index 337e816ca..afadc9c7f 100644 --- a/src/portal/package-lock.json +++ b/src/portal/package-lock.json @@ -25,7 +25,7 @@ "@ngx-translate/core": "15.0.0", "@ngx-translate/http-loader": "8.0.0", "cron-validator": "^1.3.1", - "highcharts": "^11.1.0", + "echarts": "^5.4.3", "js-yaml": "^4.1.0", "ngx-clipboard": "^15.1.0", "ngx-cookie": "^6.0.1", @@ -8126,6 +8126,20 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/echarts": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", + "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.4.4" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10030,11 +10044,6 @@ "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", "optional": true }, - "node_modules/highcharts": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.1.0.tgz", - "integrity": "sha512-vhmqq6/frteWMx0GKYWwEFL25g4OYc7+m+9KQJb/notXbNtIb8KVy+ijOF7XAFqF165cq0pdLIePAmyFY5ph3g==" - }, "node_modules/hosted-git-info": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", @@ -19341,6 +19350,19 @@ "dependencies": { "tslib": "^2.3.0" } + }, + "node_modules/zrender": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", + "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } } diff --git a/src/portal/package.json b/src/portal/package.json index 6e7990186..f65be5e6f 100644 --- a/src/portal/package.json +++ b/src/portal/package.json @@ -43,7 +43,7 @@ "@ngx-translate/core": "15.0.0", "@ngx-translate/http-loader": "8.0.0", "cron-validator": "^1.3.1", - "highcharts": "^11.1.0", + "echarts": "^5.4.3", "js-yaml": "^4.1.0", "ngx-clipboard": "^15.1.0", "ngx-cookie": "^6.0.1", diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/single-bar/single-bar.component.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/single-bar/single-bar.component.ts index 60e910595..12571c3cc 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/single-bar/single-bar.component.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/single-bar/single-bar.component.ts @@ -8,7 +8,7 @@ import { ViewChild, } from '@angular/core'; import { DangerousArtifact } from '../../../../../../../ng-swagger-gen/models/dangerous-artifact'; -import * as Highcharts from 'highcharts'; +import * as echarts from 'echarts/core'; @Component({ selector: 'app-single-bar', @@ -30,61 +30,71 @@ export class SingleBarComponent implements OnChanges { } initChart() { - (Highcharts as any).chart(this.container.nativeElement, { - credits: { - enabled: false, - }, - chart: { - backgroundColor: 'transparent', - type: 'bar', - }, + const chart = echarts.init(this.container.nativeElement); + chart.setOption({ + color: ['red', '#e64524', 'orange'], title: { text: '', }, tooltip: { - pointFormat: '{series.data.name}{point.y}', - style: { - fontSize: 12, + formatter: '{b}: {c}', + textStyle: { + fontSize: '12px', whiteSpace: 'nowrap', }, }, - plotOptions: { - pie: { - startAngle: -90, - endAngle: 90, - dataLabels: { - enabled: true, - distance: -8, - style: { - fontSize: '8px', - fontWeight: 1, - }, - pointFormat: '{point.y}', - }, - size: 50, - borderWidth: 0, - borderRadius: 2, - }, + legend: { + show: false, }, series: [ { type: 'pie', + radius: '65%', name: 'Severity', + // adjust the start angle + startAngle: 180, + itemStyle: { + borderRadius: 2, + borderWidth: 1, + }, + labelLine: { + show: false, + }, + label: { + show: true, + position: 'inside', + formatter: '{c}', + fontSize: '9px', + }, data: [ { name: 'Critical', - y: this.dangerousArtifact?.critical_cnt || 0, - color: 'red', + value: this.dangerousArtifact?.critical_cnt || 0, }, { name: 'High', - y: this.dangerousArtifact?.high_cnt || 0, - color: '#e64524', + value: this.dangerousArtifact?.high_cnt || 0, }, { name: 'Medium', - y: this.dangerousArtifact?.medium_cnt || 0, - color: 'orange', + value: this.dangerousArtifact?.medium_cnt || 0, + }, + { + // make a record to fill the bottom 50% + value: + this.dangerousArtifact?.critical_cnt + + this.dangerousArtifact?.high_cnt + + this.dangerousArtifact?.medium_cnt || 0, + itemStyle: { + // stop the chart from rendering this piece + color: 'none', + decal: { + symbol: 'none', + }, + }, + label: { + show: false, + }, }, ], }, diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.html b/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.html index ddad70cb5..786d57360 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.html +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.html @@ -96,7 +96,7 @@
-
+
diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.ts index d1620afc6..154f8ecff 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/vulnerability-database/vulnerability-summary/vulnerability-summary.component.ts @@ -1,15 +1,16 @@ import { Component, + ElementRef, EventEmitter, + HostListener, OnDestroy, OnInit, Output, + ViewChild, } from '@angular/core'; import { SecurityhubService } from '../../../../../../../ng-swagger-gen/services/securityhub.service'; import { SecuritySummary } from '../../../../../../../ng-swagger-gen/models/security-summary'; import { MessageHandlerService } from '../../../../../shared/services/message-handler.service'; -import * as Highcharts from 'highcharts'; -import highchartsAccessibility from 'highcharts/modules/accessibility'; import { getDigestLink, severityText, VUL_ID } from '../security-hub.interface'; import { HAS_STYLE_MODE, StyleMode } from '../../../../../services/theme'; import { Subscription } from 'rxjs'; @@ -19,7 +20,7 @@ import { } from '../../../../../services/event-service/event.service'; import { TranslateService } from '@ngx-translate/core'; import { DangerousArtifact } from '../../../../../../../ng-swagger-gen/models/dangerous-artifact'; -highchartsAccessibility(Highcharts); +import * as echarts from 'echarts/core'; @Component({ selector: 'app-vulnerability-summary', @@ -36,6 +37,9 @@ export class VulnerabilitySummaryComponent implements OnInit, OnDestroy { readonly severityText = severityText; readonly getDigestLink = getDigestLink; harborEventSub: Subscription; + chart: any; + @ViewChild('pieChart', { static: true }) + pieChartEle: ElementRef; constructor( private securityHubService: SecurityhubService, private messageHandler: MessageHandlerService, @@ -44,6 +48,7 @@ export class VulnerabilitySummaryComponent implements OnInit, OnDestroy { ) {} ngOnInit() { + this.chart = echarts.init(this.pieChartEle.nativeElement); this.getSummary(); if (!this.harborEventSub) { this.harborEventSub = this.event.subscribe( @@ -91,79 +96,74 @@ export class VulnerabilitySummaryComponent implements OnInit, OnDestroy { 'UNKNOWN', ]; this.translate.get([severity, c, h, m, l, n, u]).subscribe(res => { - Highcharts.chart('pie-chart', { - credits: { - enabled: false, - }, - chart: { - backgroundColor: 'transparent', - plotBackgroundColor: null, - plotBorderWidth: null, - plotShadow: false, - type: 'pie', - }, + this.chart.setOption({ + color: ['red', '#e64524', 'orange', '#007CBB', 'grey', 'green'], title: { text: '', }, tooltip: { - pointFormat: '{point.percentage:.1f}%', - }, - plotOptions: { - pie: { - dataLabels: { - enabled: false, - }, - showInLegend: true, - borderWidth: 0, - }, + formatter: '{b}: {d}%', }, legend: { align: 'left', - floating: true, - symbolRadius: 2, - itemStyle: { - fontSize: '12px', - fontWeight: '100', + left: 5, + bottom: 5, + formatter: '{a|{name}}', + textStyle: { color: this.getColorByTheme(), + width: 50, + backgroundColor: 'transparent', + rich: { + a: { + fontWeight: '100', + fontSize: '12px', + verticalAlign: 'bottom', + height: 12, + lineHeight: 15, + }, + }, }, - width: '60%', + itemWidth: 12, + itemHeight: 12, + width: '50%', }, series: [ { - innerSize: '60%', + itemStyle: { + borderRadius: 3, + borderWidth: 1, + }, + label: { + show: false, + }, + radius: ['50%', '80%'], name: res[severity], type: 'pie', - center: ['80%', '50%'], + center: ['68%', '50%'], data: [ { name: res[c], - y: summary?.critical_cnt || 0, - color: 'red', + value: summary?.critical_cnt || 0, }, { name: res[h], - y: summary?.high_cnt || 0, - color: '#e64524', + value: summary?.high_cnt || 0, }, { name: res[m], - y: summary?.medium_cnt || 0, - color: 'orange', + value: summary?.medium_cnt || 0, }, { name: res[l], - y: summary?.low_cnt || 0, - color: '#007CBB', + value: summary?.low_cnt || 0, }, { name: res[u], - y: summary?.unknown_cnt || 0, - color: 'grey', + value: summary?.unknown_cnt || 0, }, { name: res[n], - y: summary?.none_cnt || 0, - color: 'green', + value: summary?.none_cnt || 0, }, ], }, @@ -185,4 +185,9 @@ export class VulnerabilitySummaryComponent implements OnInit, OnDestroy { ? '#000' : '#fff'; } + + @HostListener('window:resize', ['$event']) + onResize(event: any) { + this.chart.resize(); + } } diff --git a/src/portal/src/app/shared/shared.module.ts b/src/portal/src/app/shared/shared.module.ts index af4e4a072..8418b0c76 100644 --- a/src/portal/src/app/shared/shared.module.ts +++ b/src/portal/src/app/shared/shared.module.ts @@ -76,9 +76,35 @@ import { HarborDatetimePipe } from './pipes/harbor-datetime.pipe'; import { RemainingTimeComponent } from './components/remaining-time/remaining-time.component'; import { LabelSelectorComponent } from './components/label-selector/label-selector.component'; import { ScrollSectionDirective } from './directives/scroll/scroll-section.directive'; -import { ScrollManagerService } from './directives/scroll/scroll-manager.service'; import { ScrollAnchorDirective } from './directives/scroll/scroll-anchor.directive'; import { AppLevelAlertsComponent } from './components/app-level-alerts/app-level-alerts.component'; +// import echarts +import * as echarts from 'echarts/core'; +import { PieChart } from 'echarts/charts'; +import { + TitleComponent, + TooltipComponent, + GridComponent, + DatasetComponent, + TransformComponent, + LegendComponent, +} from 'echarts/components'; +import { LabelLayout, UniversalTransition } from 'echarts/features'; +import { CanvasRenderer } from 'echarts/renderers'; + +// register necessary components +echarts.use([ + TitleComponent, + TooltipComponent, + GridComponent, + DatasetComponent, + TransformComponent, + PieChart, + LabelLayout, + UniversalTransition, + CanvasRenderer, + LegendComponent, +]); // ClarityIcons is publicly accessible from the browser's window object. declare const ClarityIcons: ClarityIconsApi;