From 8e4d866683063aa05cd037b160ee7d4fec0dd657 Mon Sep 17 00:00:00 2001 From: Dominik Riemer Date: Sun, 10 Sep 2023 18:32:22 +0200 Subject: [PATCH] Improve data explorer widgets (#1857) * Improve rendering of echarts widgets in data explorer * Migrate heatmap widget to echarts renderer * Add revision to correlation charts * Remove unused echarts-stats module * Remove unused css files, add license header * Improve rendering of tooltips and labels * Fix reloading behaviour in time selector * Fix format --- ui/deployment/appng5.module.mst | 13 + ui/package-lock.json | 41 +++ ui/package.json | 2 + .../echarts-transform/histogram.transform.ts | 78 +++++ .../round-values.transform.ts | 75 ++++ .../value-distribution.transform.ts | 75 ++++ ...plorer-visualisation-settings.component.ts | 2 +- .../timeRangeSelector.component.html | 3 +- ...-data-explorer-echarts-widget.directive.ts | 127 +++++++ .../base-data-explorer-widget.directive.ts | 16 +- .../echarts-widget.component.html} | 27 +- .../echarts-widget.component.scss} | 7 + ...rrelation-chart-widget-config.component.ts | 1 - .../correlation-chart-widget.component.html | 2 + .../correlation-chart-widget.component.scss | 17 - .../correlation-chart-widget.component.ts | 4 +- ...ibution-chart-widget-config.component.html | 75 +++- ...ibution-chart-widget-config.component.scss | 17 - ...tribution-chart-widget-config.component.ts | 30 +- .../distribution-chart-widget.component.html | 65 ---- .../distribution-chart-widget.component.scss | 17 - .../distribution-chart-widget.component.ts | 203 +---------- .../model/distribution-chart-widget.model.ts | 5 + .../value-heatmap.component.html | 28 -- .../value-heatmap.component.scss | 17 - .../value-heatmap/value-heatmap.component.ts | 188 ---------- .../heatmap-widget-config.component.scss | 17 - .../config/heatmap-widget-config.component.ts | 1 - .../heatmap/heatmap-widget.component.ts | 304 +---------------- ...dicator-chart-widget-config.component.scss | 17 - ...indicator-chart-widget-config.component.ts | 1 - .../indicator-chart-widget.component.scss | 17 - .../indicator-chart-widget.component.ts | 1 - .../config/map-widget-config.component.scss | 17 - .../map/config/map-widget-config.component.ts | 1 - ...-series-chart-widget-config.component.scss | 17 - ...me-series-chart-widget-config.component.ts | 1 - .../time-series-chart-widget.component.html | 2 + .../time-series-chart-widget.component.ts | 4 + .../app/data-explorer/data-explorer.module.ts | 2 - .../sp-base-echarts-renderer.ts | 320 ++++++++++++++++++ .../sp-base-single-field-echarts-renderer.ts | 127 +++++++ .../echarts-renderer/sp-heatmap-renderer.ts | 131 +++++++ .../echarts-renderer/sp-histogram-renderer.ts | 82 +++++ .../echarts-renderer/sp-pie-renderer.ts | 114 +++++++ .../sp-value-heatmap-renderer.ts | 110 ++++++ .../dataset.model.ts} | 45 ++- .../models/dataview-dashboard.model.ts | 20 +- .../registry/data-explorer-widget-registry.ts | 12 +- .../registry/data-explorer-widgets.ts | 10 +- 50 files changed, 1537 insertions(+), 971 deletions(-) create mode 100644 ui/src/app/core-ui/echarts-transform/histogram.transform.ts create mode 100644 ui/src/app/core-ui/echarts-transform/round-values.transform.ts create mode 100644 ui/src/app/core-ui/echarts-transform/value-distribution.transform.ts create mode 100644 ui/src/app/data-explorer/components/widgets/base/base-data-explorer-echarts-widget.directive.ts rename ui/src/app/data-explorer/components/widgets/{heatmap/heatmap-widget.component.html => base/echarts-widget.component.html} (70%) rename ui/src/app/data-explorer/components/widgets/{correlation-chart/config/correlation-chart-widget-config.component.scss => base/echarts-widget.component.scss} (89%) delete mode 100644 ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/distribution-chart/config/distribution-chart-widget-config.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.html delete mode 100644 ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.html delete mode 100644 ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.ts delete mode 100644 ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.scss delete mode 100644 ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.scss create mode 100644 ui/src/app/data-explorer/echarts-renderer/sp-base-echarts-renderer.ts create mode 100644 ui/src/app/data-explorer/echarts-renderer/sp-base-single-field-echarts-renderer.ts create mode 100644 ui/src/app/data-explorer/echarts-renderer/sp-heatmap-renderer.ts create mode 100644 ui/src/app/data-explorer/echarts-renderer/sp-histogram-renderer.ts create mode 100644 ui/src/app/data-explorer/echarts-renderer/sp-pie-renderer.ts create mode 100644 ui/src/app/data-explorer/echarts-renderer/sp-value-heatmap-renderer.ts rename ui/src/app/data-explorer/{components/widgets/heatmap/heatmap-widget.component.scss => models/dataset.model.ts} (52%) diff --git a/ui/deployment/appng5.module.mst b/ui/deployment/appng5.module.mst index 7751d0c599..ae5630a3b8 100644 --- a/ui/deployment/appng5.module.mst +++ b/ui/deployment/appng5.module.mst @@ -54,6 +54,19 @@ import { AvailableRolesService } from './services/available-roles.service'; import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; +import { ValueDistributionTransform } from './core-ui/echarts-transform/value-distribution.transform'; +import { HistogramTransform } from './core-ui/echarts-transform/histogram.transform'; +import { RoundValuesTransform} from './core-ui/echarts-transform/round-values.transform'; + +import * as echarts from 'echarts'; +import * as transform from 'echarts-simple-transform'; + +echarts.registerTransform(transform.aggregate); +echarts.registerTransform(ValueDistributionTransform); +echarts.registerTransform(HistogramTransform); +echarts.registerTransform(RoundValuesTransform); + + import * as $ from 'jquery'; @NgModule({ diff --git a/ui/package-lock.json b/ui/package-lock.json index ee1d2d13ad..49cc733b45 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -38,10 +38,13 @@ "angular2-uuid": "1.1.1", "codemirror": "^5.65.11", "console-browserify": "^1.2.0", + "d3-array": "^3.2.4", "dagre": "0.8.5", "datatables.net": "^1.13.1", "datatables.net-dt": "^1.13.1", "echarts": "^5.4.1", + "echarts-simple-transform": "^1.0.0", + "echarts-stat": "^1.2.0", "echarts-wordcloud": "^2.1.0", "file-saver": "2.0.5", "jquery": "^3.7.0", @@ -12014,6 +12017,24 @@ "zrender": "5.4.1" } }, + "node_modules/echarts-simple-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/echarts-simple-transform/-/echarts-simple-transform-1.0.0.tgz", + "integrity": "sha512-zidekQFwV7s2rLUBVvum9d7kbhX2uoVWqDNYWv+MciEbjBzlhdvLDzciSgeBNe9bwEiqVfGC6NP99zjo3JXDIg==", + "dependencies": { + "tslib": "2.0.3" + } + }, + "node_modules/echarts-simple-transform/node_modules/tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + }, + "node_modules/echarts-stat": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/echarts-stat/-/echarts-stat-1.2.0.tgz", + "integrity": "sha512-zLd7Kgs+tuTSeaK0VQEMNmnMivEkhvHIk1gpBtLzpRerfcIQ+Bd5XudOMmtwpaTc1WDZbA7d1V//iiBccR46Qg==" + }, "node_modules/echarts-wordcloud": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/echarts-wordcloud/-/echarts-wordcloud-2.1.0.tgz", @@ -37207,6 +37228,26 @@ } } }, + "echarts-simple-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/echarts-simple-transform/-/echarts-simple-transform-1.0.0.tgz", + "integrity": "sha512-zidekQFwV7s2rLUBVvum9d7kbhX2uoVWqDNYWv+MciEbjBzlhdvLDzciSgeBNe9bwEiqVfGC6NP99zjo3JXDIg==", + "requires": { + "tslib": "2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "echarts-stat": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/echarts-stat/-/echarts-stat-1.2.0.tgz", + "integrity": "sha512-zLd7Kgs+tuTSeaK0VQEMNmnMivEkhvHIk1gpBtLzpRerfcIQ+Bd5XudOMmtwpaTc1WDZbA7d1V//iiBccR46Qg==" + }, "echarts-wordcloud": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/echarts-wordcloud/-/echarts-wordcloud-2.1.0.tgz", diff --git a/ui/package.json b/ui/package.json index d97c00726d..78cb2f1b84 100644 --- a/ui/package.json +++ b/ui/package.json @@ -60,11 +60,13 @@ "angular2-uuid": "1.1.1", "codemirror": "^5.65.11", "console-browserify": "^1.2.0", + "d3-array": "^3.2.4", "dagre": "0.8.5", "datatables.net": "^1.13.1", "datatables.net-dt": "^1.13.1", "echarts": "^5.4.1", "echarts-wordcloud": "^2.1.0", + "echarts-simple-transform": "^1.0.0", "file-saver": "2.0.5", "jquery": "^3.7.0", "jquery-ui-dist": "1.13.2", diff --git a/ui/src/app/core-ui/echarts-transform/histogram.transform.ts b/ui/src/app/core-ui/echarts-transform/histogram.transform.ts new file mode 100644 index 0000000000..ba5c9f0861 --- /dev/null +++ b/ui/src/app/core-ui/echarts-transform/histogram.transform.ts @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + DataTransformOption, + ExternalDataTransform, + ExternalDataTransformResultItem, +} from 'echarts/types/src/data/helper/transform'; + +import { bin } from 'd3-array'; +import { OptionSourceDataArrayRows } from 'echarts/types/src/util/types'; + +export interface HistogramConfig extends DataTransformOption { + field: string; + autoBin: boolean; + numberOfBins: number; + autoDomain: boolean; + domainMin: number; + domainMax: number; +} + +export const HistogramTransform: ExternalDataTransform = { + type: 'sp:histogram', + + transform: function ( + params, + ): ExternalDataTransformResultItem | ExternalDataTransformResultItem[] { + const upstream = params.upstream; + const clonedData = upstream.cloneRawData(); + const field = params.config['field']; + const autoBin = params.config['autoBin']; + const numberOfBins = +params.config['numberOfBins']; + const autoDomain = params.config['autoDomain']; + const domainMin = +params.config['domainMin']; + const domainMax = +params.config['domainMax']; + const dimension = upstream.getDimensionInfo(field); + const values = (clonedData as any).map(row => row[dimension.index]); + values.shift(); + + let bins = bin(); + if (!autoBin && numberOfBins) { + bins = bins.thresholds(numberOfBins); + } + if (!autoDomain && domainMin && domainMax) { + bins = bins.domain([domainMin, domainMax]); + } + const d3h = bins(values); + const source: OptionSourceDataArrayRows = []; + const hist: number[] = d3h.map(item => item.length); + const binEdges = d3h.map(item => item.x0); + if (d3h.length) { + binEdges.push(d3h.at(-1).x1); + } + hist.forEach((val, index) => { + source.push([binEdges[index] + '-' + binEdges[index + 1], val]); + }); + + return { + data: source, + dimensions: ['edge', 'hist'], + }; + }, +}; diff --git a/ui/src/app/core-ui/echarts-transform/round-values.transform.ts b/ui/src/app/core-ui/echarts-transform/round-values.transform.ts new file mode 100644 index 0000000000..6c9cd3e5f3 --- /dev/null +++ b/ui/src/app/core-ui/echarts-transform/round-values.transform.ts @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + DataTransformOption, + ExternalDataTransform, + ExternalDataTransformResultItem, +} from 'echarts/types/src/data/helper/transform'; +import { DataExplorerField } from '@streampipes/platform-services'; +import { OptionSourceDataArrayRows } from 'echarts/types/src/util/types'; + +export interface RoundValuesConfig extends DataTransformOption { + fields: DataExplorerField[]; + roundingValue: number; +} + +export const RoundValuesTransform: ExternalDataTransform = { + type: 'sp:round', + + transform: function ( + params, + ): ExternalDataTransformResultItem | ExternalDataTransformResultItem[] { + const fields: string[] = ( + params.config['fields'] as DataExplorerField[] + ) + .filter(f => f.fieldCharacteristics.numeric) + .map(f => f.fullDbName); + const roundingValue = +params.config['roundingValue']; + const upstream = params.upstream; + + const dimsDef = upstream.cloneAllDimensionInfo(); + + const roundColumnIndices = dimsDef + .filter(dim => fields.indexOf(dim.name) > -1) + .map(dim => dim.index); + + const data = upstream.cloneRawData() as OptionSourceDataArrayRows; + const result = data.map((row, index) => { + if (index == 0) { + return row; + } else { + return row.map((value, index) => { + if (roundColumnIndices.indexOf(index) > -1) { + return ( + Math.round((value as number) / roundingValue) * + roundingValue + ); + } else { + return value; + } + }); + } + }); + result.shift(); + return { + dimensions: upstream.cloneAllDimensionInfo(), + data: result, + }; + }, +}; diff --git a/ui/src/app/core-ui/echarts-transform/value-distribution.transform.ts b/ui/src/app/core-ui/echarts-transform/value-distribution.transform.ts new file mode 100644 index 0000000000..a7b3a549da --- /dev/null +++ b/ui/src/app/core-ui/echarts-transform/value-distribution.transform.ts @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + DataTransformOption, + ExternalDataTransform, + ExternalDataTransformResultItem, +} from 'echarts/types/src/data/helper/transform'; + +export interface ValueDistributionConfig extends DataTransformOption { + field: string; + resolution: number; +} + +export const ValueDistributionTransform: ExternalDataTransform = + { + type: 'sp:value-distribution', + + transform: function ( + params, + ): ExternalDataTransformResultItem | ExternalDataTransformResultItem[] { + const upstream = params.upstream; + const clonedData = upstream.cloneRawData(); + const field = params.config['field']; + const resolution = +params.config['resolution']; + const dimension = upstream.getDimensionInfo(field); + + const dataResult = []; + const allValues = (clonedData as any).map( + row => row[dimension.index], + ); + allValues.shift(); + const total = allValues.length; + let currentCount = 0; + allValues.sort((a, b) => a - b); + let start = allValues[0]; + for (let i = 0; i < allValues.length; i++) { + const value = allValues[i]; + if (value < start + +resolution) { + currentCount += 1; + } + if (value >= start + resolution || i + 1 === allValues.length) { + const currentRange = + '>' + + start.toFixed(2) + + (i + 1 < allValues.length + ? '<' + allValues[i + 1].toFixed(2) + : ''); + dataResult.push([currentRange, 0, currentCount / total]); + currentCount = 0; + start = allValues[i + 1]; + } + } + + return { + data: dataResult, + dimensions: ['category', 'group', field], + }; + }, + }; diff --git a/ui/src/app/data-explorer/components/designer-panel/visualisation-settings/data-explorer-visualisation-settings.component.ts b/ui/src/app/data-explorer/components/designer-panel/visualisation-settings/data-explorer-visualisation-settings.component.ts index f509b3b977..52e95293cd 100644 --- a/ui/src/app/data-explorer/components/designer-panel/visualisation-settings/data-explorer-visualisation-settings.component.ts +++ b/ui/src/app/data-explorer/components/designer-panel/visualisation-settings/data-explorer-visualisation-settings.component.ts @@ -33,7 +33,7 @@ export class DataExplorerVisualisationSettingsComponent implements OnInit { constructor(private widgetTypeService: WidgetTypeService) {} - availableWidgets: IWidget[]; + availableWidgets: IWidget[]; ngOnInit(): void { this.availableWidgets = diff --git a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.html b/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.html index c1fbd370cb..d722812b71 100644 --- a/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.html +++ b/ui/src/app/data-explorer/components/time-selector/timeRangeSelector.component.html @@ -86,8 +86,9 @@ class="button-margin smaller-button-font-size" matTooltip="Reload" matTooltipPosition="above" + (click)="reloadData()" > - autorenew + autorenew diff --git a/ui/src/app/data-explorer/components/widgets/base/base-data-explorer-echarts-widget.directive.ts b/ui/src/app/data-explorer/components/widgets/base/base-data-explorer-echarts-widget.directive.ts new file mode 100644 index 0000000000..64b440ed40 --- /dev/null +++ b/ui/src/app/data-explorer/components/widgets/base/base-data-explorer-echarts-widget.directive.ts @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Directive, OnInit } from '@angular/core'; +import { + DataExplorerField, + DataExplorerWidgetModel, + SpQueryResult, +} from '@streampipes/platform-services'; +import { SpEchartsRenderer } from '../../../models/dataview-dashboard.model'; +import { BaseDataExplorerWidgetDirective } from './base-data-explorer-widget.directive'; +import { ECharts } from 'echarts/core'; +import { EChartsOption } from 'echarts'; +import { Subject, Subscription } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; + +@Directive() +export abstract class BaseDataExplorerEchartsWidgetDirective< + T extends DataExplorerWidgetModel, + > + extends BaseDataExplorerWidgetDirective + implements OnInit +{ + eChartsInstance: ECharts; + currentWidth: number; + currentHeight: number; + + option: EChartsOption; + + configReady = false; + latestData: SpQueryResult[]; + + renderSubject = new Subject(); + renderSubjectSubscription: Subscription; + + ngOnInit(): void { + super.ngOnInit(); + this.initOptions(); + this.renderSubjectSubscription = this.renderSubject + .pipe(debounceTime(300)) + .subscribe(() => { + this.renderChartOptions(this.latestData); + }); + } + + beforeDataFetched() {} + + onDataReceived(spQueryResult: SpQueryResult[]) { + this.renderChartOptions(spQueryResult); + this.latestData = spQueryResult; + this.setShownComponents(false, true, false, false); + } + + onResize(width: number, height: number) { + this.currentWidth = width; + this.currentHeight = height; + this.configReady = true; + this.applySize(width, height); + if (this.latestData) { + this.renderSubject.next(); + } + } + + onChartInit(ec: ECharts) { + this.eChartsInstance = ec; + this.applySize(this.currentWidth, this.currentHeight); + this.initOptions(); + } + + applySize(width: number, height: number) { + if (this.eChartsInstance) { + this.eChartsInstance.resize({ width, height }); + } + } + + renderChartOptions(spQueryResult: SpQueryResult[]): void { + this.option = { + ...this.getRenderer().render( + spQueryResult, + this.dataExplorerWidget, + { width: this.currentWidth, height: this.currentHeight }, + ), + }; + } + + triggerFieldUpdate( + selected: DataExplorerField, + addedFields: DataExplorerField[], + removedFields: DataExplorerField[], + ): DataExplorerField { + return this.updateSingleField( + selected, + this.fieldProvider.numericFields, + addedFields, + removedFields, + field => field.fieldCharacteristics.numeric, + ); + } + + refreshView() { + this.renderSubject.next(); + } + + abstract getRenderer(): SpEchartsRenderer; + + initOptions() {} + + public cleanupSubscriptions(): void { + super.cleanupSubscriptions(); + this.renderSubjectSubscription.unsubscribe(); + } +} diff --git a/ui/src/app/data-explorer/components/widgets/base/base-data-explorer-widget.directive.ts b/ui/src/app/data-explorer/components/widgets/base/base-data-explorer-widget.directive.ts index 6f087cf1fc..57a6efb0f7 100644 --- a/ui/src/app/data-explorer/components/widgets/base/base-data-explorer-widget.directive.ts +++ b/ui/src/app/data-explorer/components/widgets/base/base-data-explorer-widget.directive.ts @@ -94,6 +94,9 @@ export abstract class BaseDataExplorerWidgetDirective< resizeSub: Subscription; timeSelectionSub: Subscription; + widthOffset: number; + heightOffset: number; + requestQueue$: Subject[]> = new Subject< Observable[] >(); @@ -108,8 +111,8 @@ export abstract class BaseDataExplorerWidgetDirective< ) {} ngOnInit(): void { - const heightOffset = this.gridMode ? 70 : 65; - const widthOffset = this.gridMode ? 10 : 10; + this.heightOffset = this.gridMode ? 70 : 65; + this.widthOffset = this.gridMode ? 10 : 10; this.showData = true; const sourceConfigs = this.dataExplorerWidget.dataConfig.sourceConfigs; this.fieldProvider = @@ -182,8 +185,9 @@ export abstract class BaseDataExplorerWidgetDirective< info => { if (info.gridsterItem.id === this.dataExplorerWidget._id) { this.onResize( - this.gridsterItemComponent.width - widthOffset, - this.gridsterItemComponent.height - heightOffset, + this.gridsterItemComponent.width - this.widthOffset, + this.gridsterItemComponent.height - + this.heightOffset, ); } }, @@ -198,8 +202,8 @@ export abstract class BaseDataExplorerWidgetDirective< ); this.updateData(); this.onResize( - this.gridsterItemComponent.width - widthOffset, - this.gridsterItemComponent.height - heightOffset, + this.gridsterItemComponent.width - this.widthOffset, + this.gridsterItemComponent.height - this.heightOffset, ); } diff --git a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.html b/ui/src/app/data-explorer/components/widgets/base/echarts-widget.component.html similarity index 70% rename from ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.html rename to ui/src/app/data-explorer/components/widgets/base/echarts-widget.component.html index cc1a8228d1..80b5ecc3a0 100644 --- a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.html +++ b/ui/src/app/data-explorer/components/widgets/base/echarts-widget.component.html @@ -16,12 +16,7 @@ ~ --> -
+
-
-
-
+
diff --git a/ui/src/app/data-explorer/components/widgets/correlation-chart/config/correlation-chart-widget-config.component.scss b/ui/src/app/data-explorer/components/widgets/base/echarts-widget.component.scss similarity index 89% rename from ui/src/app/data-explorer/components/widgets/correlation-chart/config/correlation-chart-widget-config.component.scss rename to ui/src/app/data-explorer/components/widgets/base/echarts-widget.component.scss index 13cbc4aacb..a20283bc4d 100644 --- a/ui/src/app/data-explorer/components/widgets/correlation-chart/config/correlation-chart-widget-config.component.scss +++ b/ui/src/app/data-explorer/components/widgets/base/echarts-widget.component.scss @@ -15,3 +15,10 @@ * limitations under the License. * */ + +.main-panel { + width: 100%; + height: 100%; + align-content: center; + display: block; +} diff --git a/ui/src/app/data-explorer/components/widgets/correlation-chart/config/correlation-chart-widget-config.component.ts b/ui/src/app/data-explorer/components/widgets/correlation-chart/config/correlation-chart-widget-config.component.ts index 7f9c741476..a4129ae700 100644 --- a/ui/src/app/data-explorer/components/widgets/correlation-chart/config/correlation-chart-widget-config.component.ts +++ b/ui/src/app/data-explorer/components/widgets/correlation-chart/config/correlation-chart-widget-config.component.ts @@ -28,7 +28,6 @@ import { WidgetType } from '../../../../registry/data-explorer-widgets'; @Component({ selector: 'sp-data-explorer-correlation-chart-widget-config', templateUrl: './correlation-chart-widget-config.component.html', - styleUrls: ['./correlation-chart-widget-config.component.scss'], }) export class CorrelationWidgetConfigComponent extends BaseWidgetConfig< diff --git a/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.html b/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.html index f4a9b8d5ee..ad6a15e5d4 100644 --- a/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.html +++ b/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.html @@ -43,6 +43,8 @@ *ngIf="showData" [divId]="dataExplorerWidget._id" class="plotly-container" + [updateOnlyWithRevision]="true" + [revision]="revision" [data]="data" [layout]="graph.layout" [config]="graph.config" diff --git a/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.scss b/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.ts b/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.ts index 2f8f6eccf8..5e097ddfdb 100644 --- a/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.ts +++ b/ui/src/app/data-explorer/components/widgets/correlation-chart/correlation-chart-widget.component.ts @@ -28,7 +28,6 @@ import { ColorUtils } from '../utils/color-utils'; @Component({ selector: 'sp-data-explorer-correlation-chart-widget', templateUrl: './correlation-chart-widget.component.html', - styleUrls: ['./correlation-chart-widget.component.scss'], }) export class CorrelationChartWidgetComponent extends BaseDataExplorerWidgetDirective @@ -37,6 +36,7 @@ export class CorrelationChartWidgetComponent colNo = 2; fixedColNo = 2; rowNo = 2; + revision = 1; data = []; @@ -221,6 +221,7 @@ export class CorrelationChartWidgetComponent this.graph.layout.autosize = false; (this.graph.layout as any).width = width; (this.graph.layout as any).height = height; + this.revision += 1; } beforeDataFetched() {} @@ -229,6 +230,7 @@ export class CorrelationChartWidgetComponent this.prepareData(spQueryResult); this.updateAppearance(); this.setShownComponents(false, true, false, false); + this.revision += 1; } handleUpdatedFields( diff --git a/ui/src/app/data-explorer/components/widgets/distribution-chart/config/distribution-chart-widget-config.component.html b/ui/src/app/data-explorer/components/widgets/distribution-chart/config/distribution-chart-widget-config.component.html index b6707989c9..cbb2301453 100644 --- a/ui/src/app/data-explorer/components/widgets/distribution-chart/config/distribution-chart-widget-config.component.html +++ b/ui/src/app/data-explorer/components/widgets/distribution-chart/config/distribution-chart-widget-config.component.html @@ -51,6 +51,75 @@
Field
> +
+ + Auto-compute number of bins + + + Number of bins + + + + All values + + + Minimum value + + + + Maximum value + + +
+
Field
- - Resolution + + Resolution - -
- - - - - - - - - - -
diff --git a/ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.scss b/ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.ts b/ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.ts index eb9da4cacc..8e4ce4423b 100644 --- a/ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.ts +++ b/ui/src/app/data-explorer/components/widgets/distribution-chart/distribution-chart-widget.component.ts @@ -17,192 +17,22 @@ */ import { Component, OnInit } from '@angular/core'; -import { BaseDataExplorerWidgetDirective } from '../base/base-data-explorer-widget.directive'; import { DistributionChartWidgetModel } from './model/distribution-chart-widget.model'; -import { - DataExplorerField, - SpQueryResult, -} from '@streampipes/platform-services'; +import { DataExplorerField } from '@streampipes/platform-services'; +import { BaseDataExplorerEchartsWidgetDirective } from '../base/base-data-explorer-echarts-widget.directive'; +import { SpEchartsRenderer } from '../../../models/dataview-dashboard.model'; +import { DataExplorerWidgetRegistry } from '../../../registry/data-explorer-widget-registry'; +import { WidgetType } from '../../../registry/data-explorer-widgets'; @Component({ selector: 'sp-data-explorer-distribution-chart-widget', - templateUrl: './distribution-chart-widget.component.html', - styleUrls: ['./distribution-chart-widget.component.scss'], + templateUrl: '../base/echarts-widget.component.html', + styleUrls: ['../base/echarts-widget.component.scss'], }) export class DistributionChartWidgetComponent - extends BaseDataExplorerWidgetDirective + extends BaseDataExplorerEchartsWidgetDirective implements OnInit { - data = []; - latestData: SpQueryResult[]; - - rowNo = 2; - colNo = 2; - fixedColNo = 2; - - currentWidth: number; - currentHeight: number; - - graph = { - layout: { - grid: { - rows: this.rowNo, - columns: this.colNo, - }, - font: { - color: '#FFF', - }, - autosize: true, - plot_bgcolor: '#fff', - paper_bgcolor: '#fff', - }, - - config: { - modeBarButtonsToRemove: [ - 'lasso2d', - 'select2d', - 'toggleSpikelines', - 'toImage', - ], - displaylogo: false, - displayModeBar: false, - responsive: true, - }, - }; - - refreshView() { - this.updateAppearance(); - } - - transform(rows, index: number): any[] { - return rows.map(row => row[index]); - } - - prepareData(spQueryResult: SpQueryResult[]) { - this.data = []; - - const len = spQueryResult[0].allDataSeries.length; - - const even = len % this.colNo === 0; - - this.rowNo = even ? len / this.fixedColNo : (len + 1) / this.fixedColNo; - - this.colNo = len === 1 ? 1 : this.fixedColNo; - - let rowCount = 0; - let colCount = 0; - - spQueryResult[0].allDataSeries.map((group, gindex) => { - const series = group; - const finalLabels: string[] = []; - const finalValues: number[] = []; - const values: Map = new Map(); - const field = - this.dataExplorerWidget.visualizationConfig.selectedProperty; - const index = this.getColumnIndex(field, spQueryResult[0]); - const histoValues: number[] = []; - - let groupName; - - if (group['tags'] != null) { - Object.entries(group['tags']).forEach(([key, val]) => { - groupName = val; - }); - } - - groupName = groupName === undefined ? field.fullDbName : groupName; - - if (series.total > 0) { - const colValues = this.transform(series.rows, index); - colValues.forEach(value => { - histoValues.push(value); - - if (field.fieldCharacteristics.numeric) { - const roundingValue = - this.dataExplorerWidget.visualizationConfig - .roundingValue; - value = - Math.round(value / roundingValue) * roundingValue; - } - - if (!values.has(value)) { - values.set(value, 0); - } - const currentVal = values.get(value); - values.set(value, currentVal + 1); - }); - } - values.forEach((value, key) => { - finalLabels.push(key); - finalValues.push(value); - }); - - let component; - - if ( - this.dataExplorerWidget.visualizationConfig.displayType === - 'pie' - ) { - component = { - name: groupName, - values: finalValues, - labels: finalLabels, - type: 'pie', - domain: { - row: rowCount, - column: colCount, - }, - }; - - if (colCount === this.colNo - 1) { - colCount = 0; - rowCount += 1; - } else { - colCount += 1; - } - } else { - component = { - x: histoValues, - type: 'histogram', - }; - } - this.data.push(component); - }); - } - - existsLabel(labels: string[], value: string) { - return labels.indexOf(value) > -1; - } - - updateAppearance() { - this.graph.layout.paper_bgcolor = - this.dataExplorerWidget.baseAppearanceConfig.backgroundColor; - this.graph.layout.plot_bgcolor = - this.dataExplorerWidget.baseAppearanceConfig.backgroundColor; - this.graph.layout.font.color = - this.dataExplorerWidget.baseAppearanceConfig.textColor; - this.graph.layout.grid = { - rows: this.rowNo, - columns: this.colNo, - }; - } - - onResize(width: number, height: number) { - this.currentWidth = width; - this.currentHeight = height; - this.graph.layout.autosize = false; - (this.graph.layout as any).width = width; - (this.graph.layout as any).height = height; - } - - beforeDataFetched() {} - - onDataReceived(spQueryResult: SpQueryResult[]) { - this.prepareData(spQueryResult); - this.latestData = spQueryResult; - this.setShownComponents(false, true, false, false); - } - handleUpdatedFields( addedFields: DataExplorerField[], removedFields: DataExplorerField[], @@ -215,17 +45,12 @@ export class DistributionChartWidgetComponent ); } - triggerFieldUpdate( - selected: DataExplorerField, - addedFields: DataExplorerField[], - removedFields: DataExplorerField[], - ): DataExplorerField { - return this.updateSingleField( - selected, - this.fieldProvider.numericFields, - addedFields, - removedFields, - field => field.fieldCharacteristics.numeric, + getRenderer(): SpEchartsRenderer { + const widgetCategory = DataExplorerWidgetRegistry.getWidgetTemplate( + WidgetType.DistributionChart, ); + const chartType = + this.dataExplorerWidget.visualizationConfig.displayType; + return widgetCategory.renderers.find(r => r.getType() === chartType); } } diff --git a/ui/src/app/data-explorer/components/widgets/distribution-chart/model/distribution-chart-widget.model.ts b/ui/src/app/data-explorer/components/widgets/distribution-chart/model/distribution-chart-widget.model.ts index e37cbf6461..c7c7835bb9 100644 --- a/ui/src/app/data-explorer/components/widgets/distribution-chart/model/distribution-chart-widget.model.ts +++ b/ui/src/app/data-explorer/components/widgets/distribution-chart/model/distribution-chart-widget.model.ts @@ -28,6 +28,11 @@ export interface DistributionChartVisConfig extends DataExplorerVisConfig { displayType: string; roundingValue: number; resolution: number; + autoBin: boolean; + numberOfBins: number; + autoDomain: boolean; + domainMin: number; + domainMax: number; } export interface DistributionChartWidgetModel extends DataExplorerWidgetModel { diff --git a/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.html b/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.html deleted file mode 100644 index 4af8d0181a..0000000000 --- a/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.html +++ /dev/null @@ -1,28 +0,0 @@ - - -
-
-
diff --git a/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.scss b/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.ts b/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.ts deleted file mode 100644 index 9dbdc824ac..0000000000 --- a/ui/src/app/data-explorer/components/widgets/distribution-chart/value-heatmap/value-heatmap.component.ts +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import { Component, Input } from '@angular/core'; -import { EChartsOption } from 'echarts'; -import { ECharts } from 'echarts/core'; -import { SpQueryResult } from '@streampipes/platform-services'; -import { DistributionChartWidgetModel } from '../model/distribution-chart-widget.model'; - -@Component({ - selector: 'sp-data-explorer-value-heatmap-widget', - templateUrl: './value-heatmap.component.html', - styleUrls: ['./value-heatmap.component.scss'], -}) -export class SpValueHeatmapComponent { - currentWidth_: number; - currentHeight_: number; - data_: SpQueryResult[]; - - @Input() - widgetConfig: DistributionChartWidgetModel; - - configReady = true; - option: EChartsOption = { - grid: { - height: '80%', - top: '7%', - }, - tooltip: {}, - xAxis: { - type: 'category', - data: [], - }, - yAxis: { - type: 'category', - data: [], - }, - visualMap: { - min: 0, - max: 1, - calculable: true, - realtime: false, - top: '10px', - left: '10px', - inRange: { - color: [ - '#313695', - '#4575b4', - '#74add1', - '#abd9e9', - '#e0f3f8', - '#ffffbf', - '#fee090', - '#fdae61', - '#f46d43', - '#d73027', - '#a50026', - ], - }, - }, - series: [ - { - name: 'Gaussian', - type: 'heatmap', - data: [], - emphasis: { - itemStyle: { - borderColor: '#333', - borderWidth: 1, - }, - }, - animation: false, - }, - ], - }; - - dynamic: EChartsOption; - - eChartsInstance: ECharts; - - onChartInit(ec: ECharts) { - this.eChartsInstance = ec; - this.applySize(this.currentWidth_, this.currentHeight_); - this.initOptions(); - } - - initOptions() { - if (this.data_) { - const dataResult = []; - const resolution: number = - +this.widgetConfig.visualizationConfig.resolution; - const allRows = this.data_[0].allDataSeries[0].rows; - const fieldIndex = this.getFieldIndex( - this.data_[0].allDataSeries[0].headers, - ); - const allValues = allRows.map(row => row[fieldIndex]); - const total = allValues.length; - let currentCount = 0; - allValues.sort((a, b) => a - b); - let start = allValues[0]; - for (let i = 0; i < allValues.length; i++) { - const value = allValues[i]; - if (value < start + resolution) { - currentCount += 1; - } - if (value >= start + resolution || i + 1 === allValues.length) { - const currentRange = - '>' + - start.toFixed(2) + - (i + 1 < allValues.length - ? '<' + allValues[i + 1].toFixed(2) - : ''); - dataResult.push([ - currentRange, - 0, - (currentCount + 1) / total, - ]); - currentCount = 0; - start = allValues[i + 1]; - } - } - this.dynamic = this.option; - (this.dynamic.xAxis as any).data = dataResult.map(r => r[0]); - this.dynamic.series[0].data = dataResult; - this.dynamic.series[0].name = this.data_[0].headers[fieldIndex]; - if (this.eChartsInstance) { - this.eChartsInstance.setOption(this.dynamic); - } - } - } - - getFieldIndex(headers: string[]) { - return headers.indexOf( - this.widgetConfig.visualizationConfig.selectedProperty.fullDbName, - ); - } - - applySize(width: number, height: number) { - if (this.eChartsInstance) { - this.eChartsInstance.resize({ width, height }); - } - } - - @Input() - set currentWidth(currentWidth: number) { - this.currentWidth_ = currentWidth; - this.applySize(this.currentWidth_, this.currentHeight_); - } - - get currentWidth() { - return this.currentWidth_; - } - - @Input() - set currentHeight(currentHeight: number) { - this.currentHeight_ = currentHeight; - this.applySize(this.currentWidth_, this.currentHeight_); - } - - get currentHeight() { - return this.currentHeight_; - } - - @Input() - set data(data: SpQueryResult[]) { - this.data_ = data; - this.initOptions(); - } - - get data() { - return this.data_; - } -} diff --git a/ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.scss b/ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.ts b/ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.ts index e1d6cf7c8a..a3aa8f0371 100644 --- a/ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.ts +++ b/ui/src/app/data-explorer/components/widgets/heatmap/config/heatmap-widget-config.component.ts @@ -30,7 +30,6 @@ import { WidgetType } from '../../../../registry/data-explorer-widgets'; @Component({ selector: 'sp-data-explorer-heatmap-widget-config', templateUrl: './heatmap-widget-config.component.html', - styleUrls: ['./heatmap-widget-config.component.scss'], }) export class HeatmapWidgetConfigComponent extends BaseWidgetConfig diff --git a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.ts b/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.ts index d77497d8d5..5039553db6 100644 --- a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.ts +++ b/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.ts @@ -17,312 +17,40 @@ */ import { Component, OnInit } from '@angular/core'; -import { - DataExplorerField, - SpQueryResult, -} from '@streampipes/platform-services'; +import { DataExplorerField } from '@streampipes/platform-services'; -import { BaseDataExplorerWidgetDirective } from '../base/base-data-explorer-widget.directive'; import { HeatmapWidgetModel } from './model/heatmap-widget.model'; - -import { EChartsOption } from 'echarts'; -import { ECharts } from 'echarts/core'; +import { BaseDataExplorerEchartsWidgetDirective } from '../base/base-data-explorer-echarts-widget.directive'; +import { SpEchartsRenderer } from '../../../models/dataview-dashboard.model'; +import { SpHeatmapRenderer } from '../../../echarts-renderer/sp-heatmap-renderer'; @Component({ selector: 'sp-data-explorer-heatmap-widget', - templateUrl: './heatmap-widget.component.html', - styleUrls: ['./heatmap-widget.component.scss'], + templateUrl: '../base/echarts-widget.component.html', + styleUrls: ['../base/echarts-widget.component.scss'], }) export class HeatmapWidgetComponent - extends BaseDataExplorerWidgetDirective + extends BaseDataExplorerEchartsWidgetDirective implements OnInit { - eChartsInstance: ECharts; - currentWidth: number; - currentHeight: number; - - option = {}; - dynamic: EChartsOption; - - configReady = false; - ngOnInit(): void { super.ngOnInit(); - this.onSizeChanged( - this.gridsterItemComponent.width, - this.gridsterItemComponent.height, - ); - this.initOptions(); } - public refreshView() {} - - onResize(width: number, height: number) { - this.onSizeChanged(width, height); + getRenderer(): SpEchartsRenderer { + return new SpHeatmapRenderer(); } handleUpdatedFields( addedFields: DataExplorerField[], removedFields: DataExplorerField[], - ) {} - - beforeDataFetched() {} - - onDataReceived(spQueryResult: SpQueryResult[]) { - this.setShownComponents(false, true, false, false); - const dataBundle = this.convertData(spQueryResult); - if (Object.keys(this.option).length > 0) { - this.setOptions( - dataBundle[0], - dataBundle[1], - dataBundle[2], - dataBundle[3], - dataBundle[4], - ); - } - } - - onChartInit(ec: ECharts) { - this.eChartsInstance = ec; - this.applySize(this.currentWidth, this.currentHeight); - this.initOptions(); - } - - protected onSizeChanged(width: number, height: number) { - this.currentWidth = width; - this.currentHeight = height; - this.configReady = true; - this.applySize(width, height); - } - - applySize(width: number, height: number) { - if (this.eChartsInstance) { - this.eChartsInstance.resize({ width, height }); - } - } - - convertData(spQueryResult: SpQueryResult[]) { - let min = 1000000; - let max = -100000; - - const result = spQueryResult[0].allDataSeries; - // const xAxisData = this.transform(result[0].rows, 0); - - const aggregatedXData = []; - result.forEach(x => { - const localXAxisData = this.transform(x.rows, 0); - aggregatedXData.push(...localXAxisData); - }); - - const xAxisData = aggregatedXData.sort(); - - const convXAxisData = []; - xAxisData.forEach(x => { - const date = new Date(x); - const size = 2; - const year = date.getFullYear(); - const month = this.pad(date.getMonth() + 1, size); - const day = date.getDate(); - const hours = this.pad(date.getHours(), size); - const minutes = this.pad(date.getMinutes(), size); - const seconds = this.pad(date.getSeconds(), size); - const milli = this.pad(date.getMilliseconds(), 3); - - const strDate = - year + - '-' + - month + - '-' + - day + - ' ' + - hours + - ':' + - minutes + - ':' + - seconds + - '.' + - milli; - convXAxisData.push(strDate); - }); - - const heatIndex = this.getColumnIndex( - this.dataExplorerWidget.visualizationConfig.selectedHeatProperty, - spQueryResult[0], - ); - - const yAxisData = []; - const contentData = []; - - result.map((groupedList, index) => { - let groupedVal = ''; - - if (groupedList['tags'] != null) { - Object.entries(groupedList['tags']).forEach(([key, value]) => { - groupedVal = value; - }); - } - - yAxisData.push(groupedVal); - - const contentDataPure = this.transform( - result[index].rows, - heatIndex, - ); - const localMax = Math.max.apply(Math, contentDataPure); - const localMin = Math.min.apply(Math, contentDataPure); - - max = localMax > max ? localMax : max; - min = localMin < min ? localMin : min; - - const localXAxisData = this.transform(groupedList.rows, 0); - - contentDataPure.map((cnt, colIndex) => { - const currentX = localXAxisData[colIndex]; - const searchedIndex = aggregatedXData.indexOf(currentX); - contentData.push([searchedIndex, index, cnt]); - }); - }); - - const timeNames = convXAxisData; - const groupNames = yAxisData; - - this.option['tooltip'] = { - formatter(params) { - const timeIndex = params.value[0]; - const groupNameIndex = params.value[1]; - - const value = params.value[2]; - const time = timeNames[timeIndex]; - const groupName = groupNames[groupNameIndex]; - - let formattedTip = - '' + - '
    ' + - '
  • ' + - 'Time: ' + - '' + - time + - '
  • '; - - if (groupName !== '') { - formattedTip = - formattedTip + - '
  • ' + - 'Group: ' + - '' + - groupName + - '
  • '; - } - - formattedTip = - formattedTip + - '
  • ' + - 'Value: ' + - '' + - value + - '
  • ' + - '
'; - - return formattedTip; - }, - position: 'top', - }; - - if (groupNames.length === 1) { - this.option['tooltip']['position'] = ( - point, - params, - dom, - rect, - size, - ) => { - return [point[0], '10%']; - }; - } - - return [contentData, convXAxisData, yAxisData, min, max]; - } - - initOptions() { - this.option = { - tooltip: {}, - grid: { - height: '80%', - top: '7%', - }, - xAxis: { - type: 'category', - data: [], - splitArea: { - show: true, - }, - }, - yAxis: { - type: 'category', - data: [], - splitArea: { - show: true, - }, - }, - visualMap: { - min: 0, - max: 10, - calculable: true, - orient: 'vertical', - right: '5%', - top: '7%', - }, - series: [ - { - name: '', - type: 'heatmap', - data: [], - label: { - show: true, - }, - emphasis: { - itemStyle: { - shadowBlur: 10, - shadowColor: 'rgba(0, 0, 0, 0.5)', - }, - }, - }, - ], - }; - } - - setOptions( - contentData: any, - xAxisData: any, - yAxisData: any, - min: any, - max: any, ) { - this.dynamic = this.option; - this.dynamic.series[0].data = contentData; - this.dynamic.series[0].label.show = - this.dataExplorerWidget.visualizationConfig.showLabelsProperty; - this.dynamic['xAxis']['data'] = xAxisData; - this.dynamic['yAxis']['data'] = yAxisData; - this.dynamic['visualMap']['min'] = min; - this.dynamic['visualMap']['max'] = max; - if (this.eChartsInstance) { - this.eChartsInstance.setOption(this.dynamic as EChartsOption); - } - this.option = this.dynamic; - } - - transform(rows, index: number): any[] { - return rows.map(row => row[index]); - } - - pad(num, size) { - num = num.toString(); - while (num.length < size) { - num = '0' + num; - } - return num; + this.dataExplorerWidget.visualizationConfig.selectedHeatProperty = + this.triggerFieldUpdate( + this.dataExplorerWidget.visualizationConfig + .selectedHeatProperty, + addedFields, + removedFields, + ); } } diff --git a/ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.scss b/ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.ts b/ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.ts index fd39185af9..770a8aa054 100644 --- a/ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.ts +++ b/ui/src/app/data-explorer/components/widgets/indicator/config/indicator-chart-widget-config.component.ts @@ -28,7 +28,6 @@ import { WidgetType } from '../../../../registry/data-explorer-widgets'; @Component({ selector: 'sp-data-explorer-indicator-chart-widget-config', templateUrl: './indicator-chart-widget-config.component.html', - styleUrls: ['./indicator-chart-widget-config.component.scss'], }) export class IndicatorWidgetConfigComponent extends BaseWidgetConfig diff --git a/ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.scss b/ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.ts b/ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.ts index bd3d7050d1..c94bd14230 100644 --- a/ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.ts +++ b/ui/src/app/data-explorer/components/widgets/indicator/indicator-chart-widget.component.ts @@ -27,7 +27,6 @@ import { @Component({ selector: 'sp-data-explorer-indicator-chart-widget', templateUrl: './indicator-chart-widget.component.html', - styleUrls: ['./indicator-chart-widget.component.scss'], }) export class IndicatorChartWidgetComponent extends BaseDataExplorerWidgetDirective diff --git a/ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.scss b/ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.ts b/ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.ts index 006be3a30b..48beb9379f 100644 --- a/ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.ts +++ b/ui/src/app/data-explorer/components/widgets/map/config/map-widget-config.component.ts @@ -27,7 +27,6 @@ import { WidgetType } from '../../../../registry/data-explorer-widgets'; @Component({ selector: 'sp-data-explorer-map-widget-config', templateUrl: './map-widget-config.component.html', - styleUrls: ['./map-widget-config.component.scss'], }) export class MapWidgetConfigComponent extends BaseWidgetConfig diff --git a/ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.scss b/ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.scss deleted file mode 100644 index 13cbc4aacb..0000000000 --- a/ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ diff --git a/ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.ts b/ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.ts index 0e5df45a49..3f6b21fc28 100644 --- a/ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.ts +++ b/ui/src/app/data-explorer/components/widgets/time-series-chart/config/time-series-chart-widget-config.component.ts @@ -33,7 +33,6 @@ import { WidgetType } from '../../../../registry/data-explorer-widgets'; @Component({ selector: 'sp-data-explorer-time-series-chart-widget-config', templateUrl: './time-series-chart-widget-config.component.html', - styleUrls: ['./time-series-chart-widget-config.component.scss'], }) export class TimeSeriesChartWidgetConfigComponent extends BaseWidgetConfig< diff --git a/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.html b/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.html index f4a9b8d5ee..69cfdbcd36 100644 --- a/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.html +++ b/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.html @@ -44,6 +44,8 @@ [divId]="dataExplorerWidget._id" class="plotly-container" [data]="data" + [updateOnlyWithRevision]="true" + [revision]="revision" [layout]="graph.layout" [config]="graph.config" > diff --git a/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.ts b/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.ts index 1b36db696e..00e2e95f51 100644 --- a/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.ts +++ b/ui/src/app/data-explorer/components/widgets/time-series-chart/time-series-chart-widget.component.ts @@ -103,6 +103,8 @@ export class TimeSeriesChartWidgetComponent }, }; + revision = 1; + ngOnInit(): void { this.updatemenus = [ { @@ -505,6 +507,7 @@ export class TimeSeriesChartWidgetComponent (this.graph.layout as any).width = width - this.offsetRightLineChart; (this.graph.layout as any).height = height; + this.revision += 1; }, 10); } @@ -524,6 +527,7 @@ export class TimeSeriesChartWidgetComponent ); this.setShownComponents(false, true, false, false); + this.revision += 1; } handleUpdatedFields( diff --git a/ui/src/app/data-explorer/data-explorer.module.ts b/ui/src/app/data-explorer/data-explorer.module.ts index fe6cfa40fb..e902f19a35 100644 --- a/ui/src/app/data-explorer/data-explorer.module.ts +++ b/ui/src/app/data-explorer/data-explorer.module.ts @@ -96,7 +96,6 @@ import { WidgetDirective } from './components/widget/widget.directive'; import { CorrelationWidgetConfigComponent } from './components/widgets/correlation-chart/config/correlation-chart-widget-config.component'; import { TimeSelectionService } from './services/time-selection.service'; import { TooMuchDataComponent } from './components/widgets/utils/too-much-data/too-much-data.component'; -import { SpValueHeatmapComponent } from './components/widgets/distribution-chart/value-heatmap/value-heatmap.component'; import { RouterModule } from '@angular/router'; import { DataExplorerDashboardSlideViewComponent } from './components/widget-view/slide-view/data-explorer-dashboard-slide-view.component'; import { SharedUiModule } from '@streampipes/shared-ui'; @@ -210,7 +209,6 @@ export const MY_NATIVE_FORMATS = { SelectPropertiesComponent, SelectColorPropertiesComponent, SelectPropertyComponent, - SpValueHeatmapComponent, TableWidgetComponent, TableWidgetConfigComponent, MapWidgetConfigComponent, diff --git a/ui/src/app/data-explorer/echarts-renderer/sp-base-echarts-renderer.ts b/ui/src/app/data-explorer/echarts-renderer/sp-base-echarts-renderer.ts new file mode 100644 index 0000000000..5f8e55ead9 --- /dev/null +++ b/ui/src/app/data-explorer/echarts-renderer/sp-base-echarts-renderer.ts @@ -0,0 +1,320 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + SpEchartsRenderer, + WidgetBaseAppearanceConfig, +} from '../models/dataview-dashboard.model'; +import { + DataExplorerWidgetModel, + SpQueryResult, +} from '@streampipes/platform-services'; +import { EChartsOption } from 'echarts'; +import { BoxLayoutOptionMixin } from 'echarts/types/src/util/types'; +import { + DatasetOption, + GridOption, + XAXisOption, + YAXisOption, +} from 'echarts/types/dist/shared'; +import { + AxisOptions, + GeneratedDataset, + GridOptions, + TagValue, + WidgetSize, +} from '../models/dataset.model'; +import { DataTransformOption } from 'echarts/types/src/data/helper/transform'; + +export abstract class SpBaseEchartsRenderer + implements SpEchartsRenderer +{ + minimumChartWidth = 200; + minimumChartHeight = 50; + gridMargin = 60; + + abstract getType(): string; + + render( + spQueryResult: SpQueryResult[], + widgetConfig: T, + widgetSize: WidgetSize, + ): EChartsOption { + const options = this.makeBaseConfig(); + const datasets = this.toDataset(spQueryResult, widgetConfig); + this.applyOptions(datasets, options, widgetConfig, widgetSize); + return options; + } + + makeBaseConfig(): EChartsOption { + return { + legend: { + orient: 'horizontal', + top: 'top', + }, + tooltip: { + show: true, + }, + }; + } + + applySeriesPosition( + series: BoxLayoutOptionMixin[], + gridOptions: GridOption[], + ) { + series.forEach((s, index) => { + const gridOption = gridOptions[index]; + s.left = gridOption.left; + s.top = gridOption.top; + s.width = gridOption.width; + s.height = gridOption.height; + }); + } + + makeAxisOptions( + appearanceConfig: WidgetBaseAppearanceConfig, + xAxisType: 'category' | 'value' | 'time' | 'log', + yAxisType: 'category' | 'value' | 'time' | 'log', + numberOfAxes: number, + ): AxisOptions { + const xAxisOptions: XAXisOption[] = []; + const yAxisOptions: YAXisOption[] = []; + for (let i = 0; i < numberOfAxes; i++) { + xAxisOptions.push( + this.makeAxis(xAxisType, i, appearanceConfig) as XAXisOption, + ); + yAxisOptions.push( + this.makeAxis(yAxisType, i, appearanceConfig) as YAXisOption, + ); + } + + return { + xAxisOptions: xAxisOptions, + yAxisOptions: yAxisOptions, + }; + } + + makeAxis( + axisType: 'category' | 'value' | 'time' | 'log', + gridIndex: number, + appearanceConfig: WidgetBaseAppearanceConfig, + ): XAXisOption | YAXisOption { + return { + type: axisType, + gridIndex: gridIndex, + nameTextStyle: { + color: appearanceConfig.textColor, + }, + axisTick: { + lineStyle: { + color: appearanceConfig.textColor, + }, + }, + axisLabel: { + color: appearanceConfig.textColor, + }, + axisLine: { + lineStyle: { + color: appearanceConfig.textColor, + }, + }, + }; + } + + makeSeriesName(tag: TagValue): string { + return tag.tagKeys.toString() + ' ' + tag.values.toString(); + } + + addSeriesTitles( + options: EChartsOption, + series: any[], + gridOptions: GridOption[], + ): void { + options.title = series.map((s, index) => { + const grid = gridOptions[index]; + return { + text: s.name, + textStyle: { + fontSize: 12, + fontWeight: 'bold', + }, + left: grid.left, + top: (grid.top as number) - 20, + }; + }); + } + + makeGrid(tagLength: number, widgetSize: WidgetSize): GridOptions { + if (tagLength === 0) { + return { + grid: [ + { + height: widgetSize.height - 100, + width: widgetSize.width - 100, + top: this.gridMargin, + left: this.gridMargin, + right: this.gridMargin, + bottom: this.gridMargin, + }, + ], + numberOfColumns: 1, + numberOfRows: 1, + }; + } else { + const grid: GridOption[] = []; + const totalCanvasWidth = widgetSize.width - this.gridMargin * 2; + const totalCanvasHeight = widgetSize.height - this.gridMargin * 2; + const numberOfColumns = Math.max( + Math.floor(totalCanvasWidth / this.minimumChartWidth), + 1, + ); + const numberOfRows = Math.ceil(tagLength / numberOfColumns); + + const groupItemWidth = + (widgetSize.width - + (numberOfColumns * this.gridMargin + this.gridMargin)) / + numberOfColumns; + const groupItemHeight = Math.max( + this.minimumChartHeight, + (totalCanvasHeight - (numberOfRows - 1) * this.gridMargin) / + numberOfRows, + ); + + for (let i = 0; i < numberOfRows; i++) { + for (let j = 0; j < numberOfColumns; j++) { + if (i * j < tagLength) { + grid.push({ + left: Math.floor( + j * groupItemWidth + (j + 1) * this.gridMargin, + ), + top: + this.gridMargin + + i * (groupItemHeight + this.gridMargin), + width: groupItemWidth, + height: groupItemHeight, + borderWidth: 1, + }); + } + } + } + return { + grid: grid, + numberOfRows, + numberOfColumns, + }; + } + } + + toDataset( + queryResults: SpQueryResult[], + widgetConfig: T, + ): GeneratedDataset { + const datasets: DatasetOption[] = []; + const rawDataStartIndices: number[] = []; + const rawDataEndIndices: number[] = []; + const preparedDataStartIndices: number[] = []; + const preparedDataEndIndices: number[] = []; + const tagValues: TagValue[][] = []; + let initialTransformsCount = 0; + + queryResults.forEach((queryResult, index) => { + const currentDatasetSize = datasets.length; + const initialTransforms = this.initialTransforms( + widgetConfig, + index, + ); + const data = []; + data.push(queryResult.headers); + queryResult.allDataSeries.forEach(series => { + data.push(...series.rows); + }); + + const tags: TagValue[] = queryResult.allDataSeries + .map(series => series.tags) + .filter(tags => tags !== null) + .map(kv => { + return { + tagKeys: Object.keys(kv), + values: Object.values(kv), + }; + }); + + datasets.push({ + source: data, + dimensions: queryResult.headers, + }); + + if (initialTransforms.length > 0) { + initialTransformsCount++; + datasets.push({ + fromDatasetIndex: currentDatasetSize, + transform: initialTransforms, + }); + } + tags.forEach(tag => { + const filters = tag.tagKeys.map((key, index) => { + return { + dimension: key, + value: tag.values[index], + }; + }); + datasets.push({ + fromDatasetIndex: + currentDatasetSize + initialTransforms.length, + transform: { + type: 'filter', + config: { + and: filters, + }, + }, + }); + }); + tagValues.push(tags); + rawDataStartIndices.push( + currentDatasetSize + initialTransforms.length, + ); + rawDataEndIndices.push( + currentDatasetSize + initialTransforms.length + tags.length, + ); + }); + + return { + dataset: datasets, + tagValues: tagValues, + rawDataStartIndices: rawDataStartIndices, + rawDataEndIndices: rawDataEndIndices, + preparedDataStartIndices: preparedDataStartIndices, + preparedDataEndIndices: preparedDataEndIndices, + initialTransformsCount: initialTransformsCount, + }; + } + + abstract applyOptions( + datasets: GeneratedDataset, + options: EChartsOption, + widgetConfig: T, + widgetSize: WidgetSize, + ): void; + + initialTransforms( + widgetConfig: T, + sourceIndex: number, + ): DataTransformOption[] { + return []; + } +} diff --git a/ui/src/app/data-explorer/echarts-renderer/sp-base-single-field-echarts-renderer.ts b/ui/src/app/data-explorer/echarts-renderer/sp-base-single-field-echarts-renderer.ts new file mode 100644 index 0000000000..991f9a968a --- /dev/null +++ b/ui/src/app/data-explorer/echarts-renderer/sp-base-single-field-echarts-renderer.ts @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + DataExplorerField, + DataExplorerWidgetModel, +} from '@streampipes/platform-services'; +import { EChartsOption, SeriesOption } from 'echarts'; +import { SpBaseEchartsRenderer } from './sp-base-echarts-renderer'; +import { GeneratedDataset, WidgetSize } from '../models/dataset.model'; +import { DataTransformOption } from 'echarts/types/src/data/helper/transform'; +import { WidgetBaseAppearanceConfig } from '../models/dataview-dashboard.model'; + +export abstract class SpBaseSingleFieldEchartsRenderer< + T extends DataExplorerWidgetModel, + S extends SeriesOption, +> extends SpBaseEchartsRenderer { + applyOptions( + datasets: GeneratedDataset, + options: EChartsOption, + widgetConfig: T, + widgetSize: WidgetSize, + ) { + const affectedField = this.getAffectedField(widgetConfig); + const sourceIndex = affectedField.sourceIndex; + const tags = datasets.tagValues[sourceIndex]; + const rawDatasetStartIndex = datasets.rawDataStartIndices[sourceIndex]; + const numberOfCharts = tags.length === 0 ? 1 : tags.length; + const series = []; + const seriesStartIndex = this.computeSeriesStart(datasets); + + for (let i = 0; i < numberOfCharts; i++) { + datasets.dataset.push({ + fromDatasetIndex: rawDatasetStartIndex + i, + transform: this.addDatasetTransform(widgetConfig), + }); + const seriesName = + tags.length === 0 + ? this.getDefaultSeriesName(widgetConfig) + : this.makeSeriesName(tags[i]); + series.push( + this.addSeriesItem( + seriesName, + seriesStartIndex + i, + widgetConfig, + i, + ), + ); + } + const gridOptions = this.makeGrid(tags.length, widgetSize); + options.grid = gridOptions.grid; + if (this.showAxes()) { + const a = this.makeAxisOptions( + widgetConfig.baseAppearanceConfig as WidgetBaseAppearanceConfig, + this.getXAxisType(), + this.getYAxisType(), + gridOptions.numberOfRows * gridOptions.numberOfColumns, + ); + options.xAxis = a.xAxisOptions; + options.yAxis = a.yAxisOptions; + } + if (this.shouldApplySeriesPosition()) { + this.applySeriesPosition(series, gridOptions.grid); + } + if (numberOfCharts > 1) { + this.addSeriesTitles(options, series, gridOptions.grid); + } + options.dataset = datasets.dataset; + options.series = series; + this.addAdditionalConfigs(options); + } + + showAxes(): boolean { + return true; + } + + abstract addDatasetTransform(widgetConfig: T): DataTransformOption; + + abstract addAdditionalConfigs(option: EChartsOption): void; + + abstract addSeriesItem( + name: string, + datasetIndex: number, + widgetConfig: T, + index: number, + ): S; + + abstract getAffectedField(widgetConfig: T): DataExplorerField; + + private computeSeriesStart(datasets: GeneratedDataset) { + return ( + datasets.rawDataEndIndices[datasets.rawDataEndIndices.length - 1] + + 1 + ); + } + + shouldApplySeriesPosition(): boolean { + return false; + } + + getXAxisType(): 'category' | 'value' | 'time' | 'log' { + return 'category'; + } + + getYAxisType(): 'category' | 'value' | 'time' | 'log' { + return 'category'; + } + + getDefaultSeriesName(widgetConfig: T): string { + return 'Default'; + } +} diff --git a/ui/src/app/data-explorer/echarts-renderer/sp-heatmap-renderer.ts b/ui/src/app/data-explorer/echarts-renderer/sp-heatmap-renderer.ts new file mode 100644 index 0000000000..5a5e05f17a --- /dev/null +++ b/ui/src/app/data-explorer/echarts-renderer/sp-heatmap-renderer.ts @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { SpBaseEchartsRenderer } from './sp-base-echarts-renderer'; +import { HeatmapWidgetModel } from '../components/widgets/heatmap/model/heatmap-widget.model'; +import { GeneratedDataset, TagValue } from '../models/dataset.model'; +import { EChartsOption } from 'echarts'; +import { + DimensionDefinitionLoose, + OptionDataValue, + OptionSourceDataArrayRows, +} from 'echarts/types/src/util/types'; + +export class SpHeatmapRenderer extends SpBaseEchartsRenderer { + getType(): string { + return 'heatmap'; + } + + applyOptions( + datasets: GeneratedDataset, + options: EChartsOption, + widgetConfig: HeatmapWidgetModel, + ): void { + this.basicOptions(options); + + const field = widgetConfig.visualizationConfig.selectedHeatProperty; + const sourceIndex = field.sourceIndex; + + const rawDataset = datasets.dataset[sourceIndex]; + const rawDatasetSource: OptionSourceDataArrayRows = + rawDataset.source as OptionSourceDataArrayRows; + const tags = datasets.tagValues[sourceIndex]; + const heatIndex = rawDataset.dimensions.indexOf(field.fullDbName); + rawDatasetSource.shift(); + rawDatasetSource.sort((a, b) => { + const dateA = new Date(a[0]); + const dateB = new Date(b[0]); + return dateA.getTime() - dateB.getTime(); + }); + const transformedDataset = ( + rawDataset.source as OptionSourceDataArrayRows + ).map((row, index) => { + return [ + index, + this.makeTag(rawDataset.dimensions, tags, row), + row[heatIndex], + ]; + }); + + options.dataset = { source: transformedDataset }; + (options.xAxis as any).data = rawDatasetSource.map(s => { + return new Date(s[0]).toLocaleString(); + }); + options.series = [ + { + name: '', + type: 'heatmap', + datasetIndex: 0, + encode: { + itemId: 0, + value: heatIndex, + }, + label: { + show: widgetConfig.visualizationConfig.showLabelsProperty, + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, + }, + }, + ]; + } + + basicOptions(options: EChartsOption): void { + options.tooltip = {}; + options.grid = { + height: '80%', + top: '80', + }; + options.xAxis = { + type: 'category', + splitArea: { + show: true, + }, + }; + options.yAxis = { + type: 'category', + splitArea: { + show: true, + }, + }; + options.visualMap = { + calculable: true, + orient: 'horizontal', + right: '5%', + top: '20', + }; + } + + private makeTag( + dimensions: DimensionDefinitionLoose[], + tags: TagValue[], + row: Array, + ) { + if (tags.length > 0) { + const rowValues = []; + tags[0].tagKeys.forEach(key => { + const index = dimensions.indexOf(key); + rowValues.push(row[index]); + }); + return rowValues.toString(); + } + } +} diff --git a/ui/src/app/data-explorer/echarts-renderer/sp-histogram-renderer.ts b/ui/src/app/data-explorer/echarts-renderer/sp-histogram-renderer.ts new file mode 100644 index 0000000000..81be773a42 --- /dev/null +++ b/ui/src/app/data-explorer/echarts-renderer/sp-histogram-renderer.ts @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { DistributionChartWidgetModel } from '../components/widgets/distribution-chart/model/distribution-chart-widget.model'; +import { BarSeriesOption, EChartsOption } from 'echarts'; +import { SpBaseSingleFieldEchartsRenderer } from './sp-base-single-field-echarts-renderer'; +import { DataTransformOption } from 'echarts/types/src/data/helper/transform'; + +export class SpHistogramRenderer extends SpBaseSingleFieldEchartsRenderer< + DistributionChartWidgetModel, + BarSeriesOption +> { + getType(): string { + return 'histogram'; + } + + addAdditionalConfigs(option: EChartsOption) { + //do nothing + } + + addDatasetTransform( + widgetConfig: DistributionChartWidgetModel, + ): DataTransformOption { + return { + type: 'sp:histogram', + config: { + field: widgetConfig.visualizationConfig.selectedProperty + .fullDbName, + autoBin: widgetConfig.visualizationConfig.autoBin, + numberOfBins: widgetConfig.visualizationConfig.numberOfBins, + autoDomain: widgetConfig.visualizationConfig.autoDomain, + domainMin: widgetConfig.visualizationConfig.domainMin, + domainMax: widgetConfig.visualizationConfig.domainMax, + }, + }; + } + + addSeriesItem( + name: string, + datasetIndex: number, + widgetConfig: DistributionChartWidgetModel, + index: number, + ): BarSeriesOption { + return { + name, + type: 'bar', + universalTransition: true, + datasetIndex: datasetIndex, + xAxisIndex: index, + yAxisIndex: index, + encode: { x: 'edge', y: 'hist' }, + barWidth: '99.3%', + }; + } + + getAffectedField(widgetConfig: DistributionChartWidgetModel) { + return widgetConfig.visualizationConfig.selectedProperty; + } + + getYAxisType(): 'value' | 'category' | 'time' | 'log' { + return 'value'; + } + + getDefaultSeriesName(widgetConfig: DistributionChartWidgetModel): string { + return widgetConfig.visualizationConfig.selectedProperty.fullDbName; + } +} diff --git a/ui/src/app/data-explorer/echarts-renderer/sp-pie-renderer.ts b/ui/src/app/data-explorer/echarts-renderer/sp-pie-renderer.ts new file mode 100644 index 0000000000..85b8bd000c --- /dev/null +++ b/ui/src/app/data-explorer/echarts-renderer/sp-pie-renderer.ts @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { DistributionChartWidgetModel } from '../components/widgets/distribution-chart/model/distribution-chart-widget.model'; +import { EChartsOption, PieSeriesOption } from 'echarts'; +import { DataTransformOption } from 'echarts/types/src/data/helper/transform'; +import { SpBaseSingleFieldEchartsRenderer } from './sp-base-single-field-echarts-renderer'; + +export class SpPieRenderer extends SpBaseSingleFieldEchartsRenderer< + DistributionChartWidgetModel, + PieSeriesOption +> { + getType(): string { + return 'pie'; + } + + addDatasetTransform( + widgetConfig: DistributionChartWidgetModel, + ): DataTransformOption { + const field = + widgetConfig.visualizationConfig.selectedProperty.fullDbName; + return { + type: 'ecSimpleTransform:aggregate', + config: { + resultDimensions: [ + { name: 'name', from: field }, + { name: 'value', from: 'time', method: 'count' }, + ], + groupBy: field, + }, + }; + } + + addAdditionalConfigs(option: EChartsOption) { + // do nothing + } + + addSeriesItem( + name: string, + datasetIndex: number, + widgetConfig: DistributionChartWidgetModel, + ): PieSeriesOption { + return { + name, + type: 'pie', + universalTransition: true, + datasetIndex: datasetIndex, + tooltip: { + formatter: params => { + return `${params.marker} ${params.value[0]} ${params.value[1]} (${params.percent}%)`; + }, + }, + label: { + formatter: params => { + return `${params.value[0]} (${params.percent}%)`; + }, + }, + encode: { itemName: 'name', value: 'value' }, + }; + } + + initialTransforms( + widgetConfig: DistributionChartWidgetModel, + sourceIndex: number, + ): DataTransformOption[] { + const fieldSource = widgetConfig.visualizationConfig.selectedProperty; + return fieldSource.sourceIndex === sourceIndex && + fieldSource.fieldCharacteristics.numeric + ? [ + { + type: 'sp:round', + config: { + fields: [ + widgetConfig.visualizationConfig.selectedProperty, + ], + roundingValue: + widgetConfig.visualizationConfig.roundingValue, + }, + }, + ] + : []; + } + + getAffectedField(widgetConfig: DistributionChartWidgetModel) { + return widgetConfig.visualizationConfig.selectedProperty; + } + + showAxes(): boolean { + return false; + } + + shouldApplySeriesPosition(): boolean { + return true; + } + + getDefaultSeriesName(widgetConfig: DistributionChartWidgetModel): string { + return widgetConfig.visualizationConfig.selectedProperty.fullDbName; + } +} diff --git a/ui/src/app/data-explorer/echarts-renderer/sp-value-heatmap-renderer.ts b/ui/src/app/data-explorer/echarts-renderer/sp-value-heatmap-renderer.ts new file mode 100644 index 0000000000..01b68f417a --- /dev/null +++ b/ui/src/app/data-explorer/echarts-renderer/sp-value-heatmap-renderer.ts @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { DistributionChartWidgetModel } from '../components/widgets/distribution-chart/model/distribution-chart-widget.model'; +import { EChartsOption, HeatmapSeriesOption } from 'echarts'; +import { SpBaseSingleFieldEchartsRenderer } from './sp-base-single-field-echarts-renderer'; +import { DataTransformOption } from 'echarts/types/src/data/helper/transform'; + +export class SpValueHeatmapRenderer extends SpBaseSingleFieldEchartsRenderer< + DistributionChartWidgetModel, + HeatmapSeriesOption +> { + getType(): string { + return 'heatmap'; + } + + addDatasetTransform( + widgetConfig: DistributionChartWidgetModel, + ): DataTransformOption { + const field = + widgetConfig.visualizationConfig.selectedProperty.fullDbName; + return { + type: 'sp:value-distribution', + config: { + field, + resolution: widgetConfig.visualizationConfig.resolution, + }, + }; + } + + addAdditionalConfigs(options: EChartsOption) { + options.legend = { show: false }; + options.visualMap = { + min: 0, + max: 1, + calculable: true, + realtime: false, + top: '0px', + right: '50px', + orient: 'horizontal', + inRange: { + color: [ + '#313695', + '#4575b4', + '#74add1', + '#abd9e9', + '#e0f3f8', + '#ffffbf', + '#fee090', + '#fdae61', + '#f46d43', + '#d73027', + '#a50026', + ], + }, + }; + } + + addSeriesItem( + name: string, + datasetIndex: number, + widgetConfig: DistributionChartWidgetModel, + index: number, + ): HeatmapSeriesOption { + return { + universalTransition: true, + animation: true, + name: name, + type: 'heatmap', + datasetIndex: datasetIndex, + encode: { itemId: 0, value: 2 }, + xAxisIndex: index, + yAxisIndex: index, + tooltip: { + valueFormatter: value => { + if (typeof value === 'number' && isFinite(value)) { + return (value * 100).toFixed(3) + '%'; + } else { + return value as string; + } + }, + }, + emphasis: { + itemStyle: { + borderColor: '#333', + borderWidth: 1, + }, + }, + }; + } + + getAffectedField(widgetConfig: DistributionChartWidgetModel) { + return widgetConfig.visualizationConfig.selectedProperty; + } +} diff --git a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.scss b/ui/src/app/data-explorer/models/dataset.model.ts similarity index 52% rename from ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.scss rename to ui/src/app/data-explorer/models/dataset.model.ts index c4239506ee..494d11e82f 100644 --- a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.scss +++ b/ui/src/app/data-explorer/models/dataset.model.ts @@ -16,17 +16,40 @@ * */ -.plotly-container { - width: 100%; - align-self: start; - height: 100%; +import { + DatasetOption, + GridOption, + XAXisOption, + YAXisOption, +} from 'echarts/types/dist/shared'; + +export interface WidgetSize { + width: number; + height: number; +} + +export interface TagValue { + tagKeys: string[]; + values: any[]; +} + +export interface GeneratedDataset { + dataset: DatasetOption[]; + tagValues: TagValue[][]; + rawDataStartIndices: number[]; + rawDataEndIndices: number[]; + preparedDataStartIndices: number[]; + preparedDataEndIndices: number[]; + initialTransformsCount: number; +} + +export interface GridOptions { + grid: GridOption[]; + numberOfRows: number; + numberOfColumns: number; } -.main-panel { - width: 100%; - height: 100%; - text-align: center; - left: 50%; - display: inline-grid; - align-content: start; +export interface AxisOptions { + xAxisOptions: XAXisOption[]; + yAxisOptions: YAXisOption[]; } diff --git a/ui/src/app/data-explorer/models/dataview-dashboard.model.ts b/ui/src/app/data-explorer/models/dataview-dashboard.model.ts index be9dbd0754..c3b414f44a 100644 --- a/ui/src/app/data-explorer/models/dataview-dashboard.model.ts +++ b/ui/src/app/data-explorer/models/dataview-dashboard.model.ts @@ -18,15 +18,31 @@ import { GridsterConfig } from 'angular-gridster2'; import { WidgetType } from '../registry/data-explorer-widgets'; -import { DataExplorerField } from '@streampipes/platform-services'; +import { + DataExplorerField, + DataExplorerWidgetModel, + SpQueryResult, +} from '@streampipes/platform-services'; +import { EChartsOption } from 'echarts'; +import { WidgetSize } from './dataset.model'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface IDataViewDashboardConfig extends GridsterConfig {} -export interface IWidget { +export interface SpEchartsRenderer { + getType(): string; + render( + queryResult: SpQueryResult[], + widgetConfig: T, + widgetSize: WidgetSize, + ): EChartsOption; +} + +export interface IWidget { id: string; label: string; componentClass: any; + renderers?: SpEchartsRenderer[]; } export interface WidgetBaseAppearanceConfig { diff --git a/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts b/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts index 132e894c3e..26676b47d3 100644 --- a/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts +++ b/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts @@ -17,10 +17,16 @@ */ import { IWidget } from '../models/dataview-dashboard.model'; -import { WidgetTypeMap } from './data-explorer-widgets'; +import { WidgetType, WidgetTypeMap } from './data-explorer-widgets'; export class DataExplorerWidgetRegistry { - static getAvailableWidgetTemplates(): IWidget[] { - return Array.from(WidgetTypeMap.values()); + static getAvailableWidgetTemplates(): IWidget[] { + return Array.from(WidgetTypeMap.values()).sort((a, b) => + a.label.localeCompare(b.label), + ); + } + + static getWidgetTemplate(type: WidgetType) { + return WidgetTypeMap.get(type); } } diff --git a/ui/src/app/data-explorer/registry/data-explorer-widgets.ts b/ui/src/app/data-explorer/registry/data-explorer-widgets.ts index 91489dc9d6..0b27cf31a3 100644 --- a/ui/src/app/data-explorer/registry/data-explorer-widgets.ts +++ b/ui/src/app/data-explorer/registry/data-explorer-widgets.ts @@ -25,6 +25,9 @@ import { ImageWidgetComponent } from '../components/widgets/image/image-widget.c import { IndicatorChartWidgetComponent } from '../components/widgets/indicator/indicator-chart-widget.component'; import { CorrelationChartWidgetComponent } from '../components/widgets/correlation-chart/correlation-chart-widget.component'; import { DistributionChartWidgetComponent } from '../components/widgets/distribution-chart/distribution-chart-widget.component'; +import { SpPieRenderer } from '../echarts-renderer/sp-pie-renderer'; +import { SpValueHeatmapRenderer } from '../echarts-renderer/sp-value-heatmap-renderer'; +import { SpHistogramRenderer } from '../echarts-renderer/sp-histogram-renderer'; export enum WidgetType { Table, @@ -37,7 +40,7 @@ export enum WidgetType { DistributionChart, } -export const WidgetTypeMap = new Map([ +export const WidgetTypeMap = new Map>([ [ WidgetType.Table, { @@ -96,6 +99,11 @@ export const WidgetTypeMap = new Map([ id: 'distribution-chart', label: 'Distribution', componentClass: DistributionChartWidgetComponent, + renderers: [ + new SpHistogramRenderer(), + new SpPieRenderer(), + new SpValueHeatmapRenderer(), + ], }, ], ]);