From 9ec61bbe43eb68dbbd84d130965e54d2a711b318 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Wed, 2 Oct 2019 11:02:54 +0200 Subject: [PATCH] bugfix: fixing chart expressionchangedafterchanged chore: responsive chart --- .../databrowserModule/databrowser.module.ts | 14 +--- .../fileviewer/chart/chart.base.ts | 79 +++++++++++++++++++ .../fileviewer/{ => chart}/chart.interface.ts | 0 .../{ => chart}/line/line.chart.component.ts | 32 +++----- .../chart/line/line.chart.style.css | 0 .../{ => chart}/line/line.chart.template.html | 0 .../radar/radar.chart.component.ts | 25 +++--- .../chart/radar/radar.chart.style.css | 0 .../radar/radar.chart.template.html | 5 +- .../fileviewer/fileviewer.component.ts | 16 +++- .../fileviewer/fileviewer.template.html | 48 +++++------ .../fileviewer/line/line.chart.style.css | 5 -- .../fileviewer/radar/radar.chart.style.css | 5 -- .../fileviewer/util/blobToUrl.direcive.ts | 40 ---------- .../fileviewer/util/canvasToBlob.pipe.ts | 14 ---- .../kgSingleDatasetService.service.ts | 3 +- 16 files changed, 145 insertions(+), 141 deletions(-) create mode 100644 src/ui/databrowserModule/fileviewer/chart/chart.base.ts rename src/ui/databrowserModule/fileviewer/{ => chart}/chart.interface.ts (100%) rename src/ui/databrowserModule/fileviewer/{ => chart}/line/line.chart.component.ts (85%) create mode 100644 src/ui/databrowserModule/fileviewer/chart/line/line.chart.style.css rename src/ui/databrowserModule/fileviewer/{ => chart}/line/line.chart.template.html (100%) rename src/ui/databrowserModule/fileviewer/{ => chart}/radar/radar.chart.component.ts (88%) create mode 100644 src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.style.css rename src/ui/databrowserModule/fileviewer/{ => chart}/radar/radar.chart.template.html (77%) delete mode 100644 src/ui/databrowserModule/fileviewer/line/line.chart.style.css delete mode 100644 src/ui/databrowserModule/fileviewer/radar/radar.chart.style.css delete mode 100644 src/ui/databrowserModule/fileviewer/util/blobToUrl.direcive.ts delete mode 100644 src/ui/databrowserModule/fileviewer/util/canvasToBlob.pipe.ts diff --git a/src/ui/databrowserModule/databrowser.module.ts b/src/ui/databrowserModule/databrowser.module.ts index b952b012d..1e7469cda 100644 --- a/src/ui/databrowserModule/databrowser.module.ts +++ b/src/ui/databrowserModule/databrowser.module.ts @@ -11,9 +11,9 @@ import { FilterDataEntriesByRegion } from "./util/filterDataEntriesByRegion.pipe import { TooltipModule } from "ngx-bootstrap/tooltip"; import { PreviewComponent } from "./preview/preview.component"; import { FileViewer } from "./fileviewer/fileviewer.component"; -import { RadarChart } from "./fileviewer/radar/radar.chart.component"; +import { RadarChart } from "./fileviewer/chart/radar/radar.chart.component"; import { ChartsModule } from "ng2-charts"; -import { LineChart } from "./fileviewer/line/line.chart.component"; +import { LineChart } from "./fileviewer/chart/line/line.chart.component"; import { DedicatedViewer } from "./fileviewer/dedicated/dedicated.component"; import { Chart } from 'chart.js' import { AtlasViewerConstantsServices } from "src/atlasViewer/atlasViewer.constantService.service"; @@ -32,8 +32,6 @@ import { GetKgSchemaIdFromFullIdPipe } from "./util/getKgSchemaIdFromFullId.pipe import { PreviewFileIconPipe } from "./preview/previewFileIcon.pipe"; import { PreviewFileTypePipe } from "./preview/previewFileType.pipe"; import { SingleDatasetListView } from "./singleDataset/listView/singleDatasetListView.component"; -import { CanvastoBlobPipe } from "./fileviewer/util/canvasToBlob.pipe"; -import { BlobToUrlDirective } from "./fileviewer/util/blobToUrl.direcive"; @NgModule({ imports:[ @@ -58,11 +56,6 @@ import { BlobToUrlDirective } from "./fileviewer/util/blobToUrl.direcive"; SingleDatasetView, SingleDatasetListView, - /** - * directives - */ - BlobToUrlDirective, - /** * pipes */ @@ -76,8 +69,7 @@ import { BlobToUrlDirective } from "./fileviewer/util/blobToUrl.direcive"; RegionBackgroundToRgbPipe, GetKgSchemaIdFromFullIdPipe, PreviewFileIconPipe, - PreviewFileTypePipe, - CanvastoBlobPipe + PreviewFileTypePipe ], exports:[ DataBrowser, diff --git a/src/ui/databrowserModule/fileviewer/chart/chart.base.ts b/src/ui/databrowserModule/fileviewer/chart/chart.base.ts new file mode 100644 index 000000000..fdc61a3bb --- /dev/null +++ b/src/ui/databrowserModule/fileviewer/chart/chart.base.ts @@ -0,0 +1,79 @@ +import { ViewChild, ElementRef } from "@angular/core"; +import { SafeUrl, DomSanitizer } from "@angular/platform-browser"; +import { Subject, Subscription, Observable, from, interval } from "rxjs"; +import { mapTo, map, shareReplay, switchMapTo, take, switchMap, filter } from "rxjs/operators"; + +export class ChartBase{ + @ViewChild('canvas') canvas: ElementRef + + private _csvData: string + + public csvDataUrl: SafeUrl + public csvTitle: string + public imageTitle: string + + private _subscriptions: Subscription[] = [] + private _newChart$: Subject<any> = new Subject() + + private _csvUrl: string + private _pngUrl: string + + public csvUrl$: Observable<SafeUrl> + public pngUrl$: Observable<SafeUrl> + + constructor(private sanitizer: DomSanitizer){ + this.csvUrl$ = this._newChart$.pipe( + mapTo(this._csvData), + map(data => { + const blob = new Blob([data], { type: 'data:text/csv;charset=utf-8' }) + if (this._csvUrl) window.URL.revokeObjectURL(this._csvUrl) + this._csvUrl = window.URL.createObjectURL(blob) + return this.sanitizer.bypassSecurityTrustUrl(this._csvUrl) + }), + shareReplay(1) + ) + + this.pngUrl$ = this._newChart$.pipe( + switchMapTo( + interval(500).pipe( + map(() => this.canvas && this.canvas.nativeElement), + filter(v => !!v), + take(1), + switchMap(el => + from( + new Promise(rs => el.toBlob(blob => rs(blob), 'image/png')) + ) as Observable<Blob> + ) + ) + ), + map(blob => { + if (this._pngUrl) window.URL.revokeObjectURL(this._pngUrl) + this._pngUrl = window.URL.createObjectURL(blob) + return this.sanitizer.bypassSecurityTrustUrl(this._pngUrl) + }), + shareReplay(1) + ) + + // necessary + this._subscriptions.push( + this.pngUrl$.subscribe() + ) + + this._subscriptions.push( + this.csvUrl$.subscribe() + ) + } + + superOnDestroy(){ + if (this._csvUrl) window.URL.revokeObjectURL(this._csvUrl) + if (this._pngUrl) window.URL.revokeObjectURL(this._pngUrl) + while(this._subscriptions.length > 0) this._subscriptions.pop().unsubscribe() + } + + generateNewCsv(csvData: string){ + this._csvData = csvData + this.csvDataUrl = this.sanitizer.bypassSecurityTrustUrl(`data:text/csv;charset=utf-8,${csvData}`) + this._newChart$.next(null) + } + +} \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/chart.interface.ts b/src/ui/databrowserModule/fileviewer/chart/chart.interface.ts similarity index 100% rename from src/ui/databrowserModule/fileviewer/chart.interface.ts rename to src/ui/databrowserModule/fileviewer/chart/chart.interface.ts diff --git a/src/ui/databrowserModule/fileviewer/line/line.chart.component.ts b/src/ui/databrowserModule/fileviewer/chart/line/line.chart.component.ts similarity index 85% rename from src/ui/databrowserModule/fileviewer/line/line.chart.component.ts rename to src/ui/databrowserModule/fileviewer/chart/line/line.chart.component.ts index 6e456f480..4defd73c0 100644 --- a/src/ui/databrowserModule/fileviewer/line/line.chart.component.ts +++ b/src/ui/databrowserModule/fileviewer/chart/line/line.chart.component.ts @@ -1,9 +1,10 @@ -import { Component, Input, OnChanges, ElementRef, ViewChild } from '@angular/core' +import { Component, Input, OnChanges } from '@angular/core' import { DatasetInterface, ChartColor, ScaleOptionInterface, LegendInterface, TitleInterfacce, applyOption, CommonChartInterface } from '../chart.interface' import { ChartOptions, LinearTickOptions,ChartDataSets } from 'chart.js'; import { Color } from 'ng2-charts'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { ChartBase } from '../chart.base'; @Component({ selector : `line-chart`, @@ -13,9 +14,8 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; ], exportAs: 'iavLineChart' }) -export class LineChart implements OnChanges, CommonChartInterface{ +export class LineChart extends ChartBase implements OnChanges, CommonChartInterface{ - @ViewChild('canvas') canvas: ElementRef /** * labels of each of the columns, spider web edges @@ -107,8 +107,12 @@ export class LineChart implements OnChanges, CommonChartInterface{ shapedLineChartDatasets: ChartDataSets[] - constructor(private sanitizer:DomSanitizer){ - + constructor(sanitizer:DomSanitizer){ + super(sanitizer) + } + + ngOnInit(){ + this.pngUrl$.subscribe(console.log) } ngOnChanges(){ @@ -139,19 +143,15 @@ export class LineChart implements OnChanges, CommonChartInterface{ : this.getDataPointString(input.y) } - public csvDataUrl: SafeUrl - public csvTitle: string - public imageTitle: string - private generateDataUrl(){ const row0 = [this.chartOption.scales.xAxes[0].scaleLabel.labelString].concat(this.chartOption.scales.yAxes[0].scaleLabel.labelString).join(',') const maxRows = this.lineDatasets.reduce((acc, lds) => Math.max(acc, lds.data.length), 0) const rows = Array.from(Array(maxRows)).map((_,idx) => [idx.toString()].concat(this.shapedLineChartDatasets.map(v => v.data[idx] ? this.getDataPointString(v.data[idx]) : '').join(','))).join('\n') const csvData = `${row0}\n${rows}` - this.csvDataUrl = this.sanitizer.bypassSecurityTrustUrl(`data:text/csv;charset=utf-8,${csvData}`) - this.csvTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.csv` + this.generateNewCsv(csvData) + this.csvTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.csv` this.imageTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.png` } @@ -164,16 +164,6 @@ export class LineChart implements OnChanges, CommonChartInterface{ return `Untitled` } } - - get graphTitleAsString():string{ - try{ - return this.chartOption.title.text.constructor === Array - ? (this.chartOption.title.text as string[]).join(' ') - : this.chartOption.title.text as string - }catch(e){ - return `Untitled` - } - } } diff --git a/src/ui/databrowserModule/fileviewer/chart/line/line.chart.style.css b/src/ui/databrowserModule/fileviewer/chart/line/line.chart.style.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/ui/databrowserModule/fileviewer/line/line.chart.template.html b/src/ui/databrowserModule/fileviewer/chart/line/line.chart.template.html similarity index 100% rename from src/ui/databrowserModule/fileviewer/line/line.chart.template.html rename to src/ui/databrowserModule/fileviewer/chart/line/line.chart.template.html diff --git a/src/ui/databrowserModule/fileviewer/radar/radar.chart.component.ts b/src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.component.ts similarity index 88% rename from src/ui/databrowserModule/fileviewer/radar/radar.chart.component.ts rename to src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.component.ts index 2b97314d9..df4c7edfd 100644 --- a/src/ui/databrowserModule/fileviewer/radar/radar.chart.component.ts +++ b/src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.component.ts @@ -1,10 +1,10 @@ -import { Component, Input, OnChanges, ViewChild, ElementRef } from '@angular/core' +import { Component, Input, OnChanges, ViewChild, ElementRef, OnInit, OnDestroy } from '@angular/core' import { DatasetInterface, ChartColor, ScaleOptionInterface, TitleInterfacce, LegendInterface, applyOption, CommonChartInterface } from '../chart.interface'; import { Color } from 'ng2-charts'; -import { SafeUrl, DomSanitizer } from '@angular/platform-browser'; +import { DomSanitizer } from '@angular/platform-browser'; import { RadialChartOptions } from 'chart.js' - +import { ChartBase } from '../chart.base'; @Component({ selector : `radar-chart`, templateUrl : './radar.chart.template.html', @@ -13,9 +13,8 @@ import { RadialChartOptions } from 'chart.js' ], exportAs: 'iavRadarChart' }) -export class RadarChart implements OnChanges, CommonChartInterface{ +export class RadarChart extends ChartBase implements OnDestroy, OnChanges, CommonChartInterface { - @ViewChild('canvas') canvas : ElementRef /** * labels of each of the columns, spider web edges */ @@ -94,8 +93,12 @@ export class RadarChart implements OnChanges, CommonChartInterface{ datasets : [] } - constructor(private sanitizer: DomSanitizer){ + constructor(sanitizer: DomSanitizer){ + super(sanitizer) + } + ngOnDestroy(){ + this.superOnDestroy() } ngOnChanges(){ @@ -118,20 +121,16 @@ export class RadarChart implements OnChanges, CommonChartInterface{ this.generateDataUrl() } - public csvDataUrl: SafeUrl - public csvTitle: string - public imageTitle: string - private generateDataUrl(){ const row0 = ['Receptors', ...this.chartDataset.datasets.map(ds => ds.label || 'no label')].join(',') const otherRows = (this.chartDataset.labels as string[]) .map((label, index) => [ label, ...this.chartDataset.datasets.map(ds => ds.data[index]) ].join(',')).join('\n') const csvData = `${row0}\n${otherRows}` - this.csvDataUrl = this.sanitizer.bypassSecurityTrustUrl(`data:text/csv;charset=utf-8,${csvData}`) - this.csvTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.csv` + this.generateNewCsv(csvData) - this.imageTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.png` + this.csvTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.csv` + this.imageTitle = `${this.getGraphTitleAsString().replace(/\s/g, '_')}.png` } private getGraphTitleAsString():string{ diff --git a/src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.style.css b/src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.style.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/ui/databrowserModule/fileviewer/radar/radar.chart.template.html b/src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.template.html similarity index 77% rename from src/ui/databrowserModule/fileviewer/radar/radar.chart.template.html rename to src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.template.html index 1782e4b0c..17defea39 100644 --- a/src/ui/databrowserModule/fileviewer/radar/radar.chart.template.html +++ b/src/ui/databrowserModule/fileviewer/chart/radar/radar.chart.template.html @@ -16,7 +16,6 @@ </canvas> </div> -<span -*ngIf="chartDataset.datasets.length === 0 || chartDataset.labels.length === 0"> -datasets and labels are required to display radar +<span *ngIf="chartDataset.datasets.length === 0 || chartDataset.labels.length === 0"> + datasets and labels are required to display radar </span> \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/fileviewer.component.ts b/src/ui/databrowserModule/fileviewer/fileviewer.component.ts index 37eaa626f..020532d08 100644 --- a/src/ui/databrowserModule/fileviewer/fileviewer.component.ts +++ b/src/ui/databrowserModule/fileviewer/fileviewer.component.ts @@ -1,8 +1,8 @@ -import { Component, Input, ViewChild, ElementRef, Inject, Optional, OnChanges } from '@angular/core' +import { Component, Input, Inject, Optional, OnChanges, ViewChild, ChangeDetectorRef } from '@angular/core' import { ViewerPreviewFile } from 'src/services/state/dataStore.store'; import { MAT_DIALOG_DATA } from '@angular/material'; -import { CommonChartInterface } from './chart.interface'; +import { ChartBase } from './chart/chart.base'; @Component({ @@ -14,14 +14,22 @@ import { CommonChartInterface } from './chart.interface'; }) export class FileViewer implements OnChanges{ + + childChart: ChartBase + + @ViewChild('childChart') + set setChildChart(childChart:ChartBase){ + this.childChart = childChart + this.cdr.detectChanges() + } + /** * fetched directly from KG */ @Input() previewFile : ViewerPreviewFile - - @ViewChild('childChart') childChart: CommonChartInterface constructor( + private cdr: ChangeDetectorRef, @Optional() @Inject(MAT_DIALOG_DATA) data ){ if (data) this.previewFile = data.previewFile diff --git a/src/ui/databrowserModule/fileviewer/fileviewer.template.html b/src/ui/databrowserModule/fileviewer/fileviewer.template.html index 99fdf6575..ddcde7e8a 100644 --- a/src/ui/databrowserModule/fileviewer/fileviewer.template.html +++ b/src/ui/databrowserModule/fileviewer/fileviewer.template.html @@ -22,24 +22,28 @@ <!-- data container --> <div [ngSwitch]="previewFile.data.chartType" *ngSwitchCase="'application/json'"> - <radar-chart - #childChart="iavRadarChart" - [colors]="previewFile.data.colors" - [options]="previewFile.data.chartOptions" - [labels]="previewFile.data.labels" - [radarDatasets]="previewFile.data.datasets" - *ngSwitchCase="'radar'"> - - </radar-chart> - <line-chart - #childChart="iavLineChart" - - [colors]="previewFile.data.colors" - [options]="previewFile.data.chartOptions" - [lineDatasets]="previewFile.data.datasets" - *ngSwitchCase="'line'"> - - </line-chart> + + <ng-container *ngSwitchCase="'radar'"> + <radar-chart + #childChart="iavRadarChart" + [colors]="previewFile.data.colors" + [options]="previewFile.data.chartOptions" + [labels]="previewFile.data.labels" + [radarDatasets]="previewFile.data.datasets"> + + </radar-chart> + + </ng-container> + + <ng-container *ngSwitchCase="'line'"> + <line-chart + #childChart="iavLineChart" + [colors]="previewFile.data.colors" + [options]="previewFile.data.chartOptions" + [lineDatasets]="previewFile.data.datasets"> + + </line-chart> + </ng-container> <div *ngSwitchDefault> The json file is not a chart. I mean, we could dump the json, but would it really help? </div> @@ -93,13 +97,9 @@ <a mat-icon-button - [hidden]="!iavBlobToUrlDirective.url" - - [iav-blob-to-url]="(childChart && childChart.canvas && childChart.canvas.nativeElement) | canvasToBlobPipe | async" - #iavBlobToUrlDirective="iavBlobToUrl" - + *ngIf="childChart" target="_blank" - [href]="iavBlobToUrlDirective.url" + [href]="childChart.pngUrl$ | async" [download]="childChart && childChart.imageTitle" matTooltip="Download chart as an image"> diff --git a/src/ui/databrowserModule/fileviewer/line/line.chart.style.css b/src/ui/databrowserModule/fileviewer/line/line.chart.style.css deleted file mode 100644 index 4a9f715af..000000000 --- a/src/ui/databrowserModule/fileviewer/line/line.chart.style.css +++ /dev/null @@ -1,5 +0,0 @@ -:host -{ - display:block; - margin:1em; -} \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/radar/radar.chart.style.css b/src/ui/databrowserModule/fileviewer/radar/radar.chart.style.css deleted file mode 100644 index 4a9f715af..000000000 --- a/src/ui/databrowserModule/fileviewer/radar/radar.chart.style.css +++ /dev/null @@ -1,5 +0,0 @@ -:host -{ - display:block; - margin:1em; -} \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/util/blobToUrl.direcive.ts b/src/ui/databrowserModule/fileviewer/util/blobToUrl.direcive.ts deleted file mode 100644 index 2c2f7ca20..000000000 --- a/src/ui/databrowserModule/fileviewer/util/blobToUrl.direcive.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Directive, Input, OnChanges, OnDestroy } from "@angular/core"; -import { SafeUrl, DomSanitizer } from "@angular/platform-browser"; - -/** - * URL.createObjectURL does not get GC'ed. - * so their initialisation and destroy need to be handled by angular life cycle - * thus, directive is needed, rather than a pipe - */ - -@Directive({ - selector: '[iav-blob-to-url]', - exportAs: 'iavBlobToUrl' -}) - -export class BlobToUrlDirective implements OnChanges, OnDestroy{ - - public url:SafeUrl - - private _url: string - - constructor(private sanitizer: DomSanitizer){ - - } - - @Input('iav-blob-to-url') - blob: Blob - - ngOnChanges(){ - this.ngOnDestroy() - if (this.blob) { - this._url = window.URL.createObjectURL(this.blob) - this.url = this.sanitizer.bypassSecurityTrustUrl(this._url) - } - } - - ngOnDestroy(){ - this.url = null - if (this._url) window.URL.revokeObjectURL(this._url) - } -} \ No newline at end of file diff --git a/src/ui/databrowserModule/fileviewer/util/canvasToBlob.pipe.ts b/src/ui/databrowserModule/fileviewer/util/canvasToBlob.pipe.ts deleted file mode 100644 index f61a6350c..000000000 --- a/src/ui/databrowserModule/fileviewer/util/canvasToBlob.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' - -@Pipe({ - name: 'canvasToBlobPipe' -}) - -export class CanvastoBlobPipe implements PipeTransform{ - public transform(canvas:HTMLCanvasElement): Promise<Blob>{ - if (!canvas) return Promise.resolve(null) - return new Promise(resolve => { - canvas.toBlob(blob => resolve(blob), 'image/png') - }) - } -} \ No newline at end of file diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/ui/databrowserModule/kgSingleDatasetService.service.ts index 62e94d104..c6997ecc4 100644 --- a/src/ui/databrowserModule/kgSingleDatasetService.service.ts +++ b/src/ui/databrowserModule/kgSingleDatasetService.service.ts @@ -41,7 +41,8 @@ export class KgSingleDatasetService implements OnDestroy{ } } - public datasetHasPreview({ name } : { name: string }){ + public datasetHasPreview({ name } : { name: string } = { name: null }){ + if (!name) throw new Error('kgSingleDatasetService#datasetHashPreview name must be defined') const _url = new URL(`${this.constantService.backendUrl}datasets/hasPreview`) const searchParam = _url.searchParams searchParam.set('datasetName', name) -- GitLab