This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch improve-data-explorer-widgets in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 08a0b28444d2025ca073ed5cc55efd0783f22050 Author: Dominik Riemer <[email protected]> AuthorDate: Mon Aug 28 23:02:26 2023 +0200 Migrate heatmap widget to echarts renderer --- .../base-data-explorer-echarts-widget.directive.ts | 15 ++ .../distribution-chart-widget.component.ts | 14 -- .../widgets/heatmap/heatmap-widget.component.html | 53 ----- .../widgets/heatmap/heatmap-widget.component.scss | 32 --- .../widgets/heatmap/heatmap-widget.component.ts | 265 +-------------------- .../echarts-renderer/sp-heatmap-renderer.ts | 104 +++++++- 6 files changed, 127 insertions(+), 356 deletions(-) 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 index 7bf1e1dcb..64b440ed4 100644 --- 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 @@ -18,6 +18,7 @@ import { Directive, OnInit } from '@angular/core'; import { + DataExplorerField, DataExplorerWidgetModel, SpQueryResult, } from '@streampipes/platform-services'; @@ -97,6 +98,20 @@ export abstract class BaseDataExplorerEchartsWidgetDirective< }; } + 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(); } 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 98183b228..8e4ce4423 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 @@ -45,20 +45,6 @@ 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<DistributionChartWidgetModel> { const widgetCategory = DataExplorerWidgetRegistry.getWidgetTemplate( WidgetType.DistributionChart, diff --git a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.html b/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.html deleted file mode 100644 index cc1a8228d..000000000 --- a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.html +++ /dev/null @@ -1,53 +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. - ~ - --> - -<div - fxFlex="100" - fxLayoutAlign="center center" - fxLayout="column" - class="main-panel" -> - <sp-no-data-in-date-range - *ngIf="showNoDataInDateRange" - [viewDateRange]="timeSettings" - class="h-100" - > - </sp-no-data-in-date-range> - - <sp-too-much-data - [amountOfEvents]="amountOfTooMuchEvents" - (loadDataWithTooManyEventsEmitter)="loadDataWithTooManyEvents()" - *ngIf="showTooMuchData" - class="h-100" - > - </sp-too-much-data> - - <div class="value-panel"> - <div - echarts - [options]="option" - [ngStyle]="{ width: currentWidth, height: '100%' }" - (chartInit)="onChartInit($event)" - [merge]="dynamic" - [ngStyle]=" - showData ? { visibility: 'visible' } : { visibility: 'hidden' } - " - *ngIf="configReady" - ></div> - </div> -</div> diff --git a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.scss b/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.scss deleted file mode 100644 index c4239506e..000000000 --- a/ui/src/app/data-explorer/components/widgets/heatmap/heatmap-widget.component.scss +++ /dev/null @@ -1,32 +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. - * - */ - -.plotly-container { - width: 100%; - align-self: start; - height: 100%; -} - -.main-panel { - width: 100%; - height: 100%; - text-align: center; - left: 50%; - display: inline-grid; - align-content: start; -} 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 f6188d9db..5039553db 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,16 +17,12 @@ */ import { Component, OnInit } from '@angular/core'; -import { - DataExplorerField, - SpQueryResult, -} from '@streampipes/platform-services'; +import { DataExplorerField } from '@streampipes/platform-services'; import { HeatmapWidgetModel } from './model/heatmap-widget.model'; - -import { EChartsOption } from 'echarts'; 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', @@ -37,259 +33,24 @@ export class HeatmapWidgetComponent extends BaseDataExplorerEchartsWidgetDirective<HeatmapWidgetModel> implements OnInit { - dynamic: EChartsOption; - ngOnInit(): void { super.ngOnInit(); } - 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], - ); - } - } - - 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 = - '<style>' + - 'ul {margin: 0px; padding: 0px; list-style-type: none; text-align: left}' + - '</style>' + - '<ul>' + - '<li><b>' + - 'Time: ' + - '</b>' + - time + - '</li>'; - - if (groupName !== '') { - formattedTip = - formattedTip + - '<li><b>' + - 'Group: ' + - '</b>' + - groupName + - '</li>'; - } - - formattedTip = - formattedTip + - '<li><b>' + - 'Value: ' + - '</b>' + - value + - '</li>' + - '</ul>'; - - 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; - } - getRenderer(): SpEchartsRenderer<HeatmapWidgetModel> { - return undefined; + return new SpHeatmapRenderer(); } - protected handleUpdatedFields( + handleUpdatedFields( addedFields: DataExplorerField[], removedFields: DataExplorerField[], - ) {} - - refreshView() {} + ) { + this.dataExplorerWidget.visualizationConfig.selectedHeatProperty = + this.triggerFieldUpdate( + this.dataExplorerWidget.visualizationConfig + .selectedHeatProperty, + addedFields, + removedFields, + ); + } } 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 index d069d63f5..5a5e05f17 100644 --- a/ui/src/app/data-explorer/echarts-renderer/sp-heatmap-renderer.ts +++ b/ui/src/app/data-explorer/echarts-renderer/sp-heatmap-renderer.ts @@ -18,20 +18,114 @@ import { SpBaseEchartsRenderer } from './sp-base-echarts-renderer'; import { HeatmapWidgetModel } from '../components/widgets/heatmap/model/heatmap-widget.model'; -import { GeneratedDataset, WidgetSize } from '../models/dataset.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<HeatmapWidgetModel> { + getType(): string { + return 'heatmap'; + } + applyOptions( datasets: GeneratedDataset, options: EChartsOption, widgetConfig: HeatmapWidgetModel, - widgetSize: WidgetSize, ): void { - // TODO + 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)', + }, + }, + }, + ]; } - getType(): string { - return 'heatmap'; + 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<OptionDataValue>, + ) { + if (tags.length > 0) { + const rowValues = []; + tags[0].tagKeys.forEach(key => { + const index = dimensions.indexOf(key); + rowValues.push(row[index]); + }); + return rowValues.toString(); + } } }
