This is an automated email from the ASF dual-hosted git repository. mfholz pushed a commit to branch status-heatmap-fix in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 13ce1314a8105a4f298efd399061764c25e3c3e8 Author: Marcelfrueh <[email protected]> AuthorDate: Mon Mar 31 13:08:53 2025 +0200 fix: resolve color mapping issue --- .../color-mapping-options-config.component.ts | 21 +-- .../config/pie-chart-widget-config.component.html | 4 + .../config/pie-chart-widget-config.component.ts | 2 +- .../charts/pie/model/pie-chart-widget.model.ts | 2 +- .../components/charts/pie/pie-renderer.service.ts | 17 +- .../status-heatmap-widget-config.component.html | 4 + .../status-heatmap-widget-config.component.ts | 4 +- .../model/status-heatmap-widget.model.ts | 2 +- .../status-heatmap-renderer.service.ts | 172 ++++++++++----------- .../services/color-mapping.service.ts | 30 ++-- 10 files changed, 133 insertions(+), 125 deletions(-) diff --git a/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.ts b/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.ts index f31dececc7..f52bf378ce 100644 --- a/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.ts +++ b/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.ts @@ -37,6 +37,8 @@ export class ColorMappingOptionsConfigComponent implements OnInit, OnChanges { @Input() selectedProperty: DataExplorerField; + @Input() showCustomColorMapping: boolean; + @Output() viewRefreshEmitter: EventEmitter<void> = new EventEmitter<void>(); @@ -45,15 +47,15 @@ export class ColorMappingOptionsConfigComponent implements OnInit, OnChanges { { value: string; label: string; color: string }[] > = new EventEmitter(); + @Output() showCustomColorMappingChange = new EventEmitter<boolean>(); + protected isSelectedPropertyBoolean: boolean; - protected showCustomColorMapping: boolean; private wasPreviousFieldBoolean: boolean; constructor(private colorMappingService: ColorMappingService) {} ngOnInit(): void { this.isSelectedPropertyBoolean = this.isBooleanPropertySelected(); - this.showCustomColorMapping ??= false; this.resetColorMappings(); } @@ -68,10 +70,8 @@ export class ColorMappingOptionsConfigComponent implements OnInit, OnChanges { } resetColorMappings(): void { - const isNowBoolean = this.isBooleanPropertySelected(); - if (!this.showCustomColorMapping) { - if (isNowBoolean) { + if (this.isBooleanPropertySelected()) { this.colorMapping = [ { value: 'true', label: '', color: '#66BB66' }, { value: 'false', label: '', color: '#BB6666' }, @@ -80,7 +80,7 @@ export class ColorMappingOptionsConfigComponent implements OnInit, OnChanges { this.colorMapping = []; } } - if (isNowBoolean) { + if (this.isBooleanPropertySelected()) { if ( !(this.colorMapping ?? []).some( mapping => @@ -97,9 +97,11 @@ export class ColorMappingOptionsConfigComponent implements OnInit, OnChanges { this.colorMapping = []; } } - this.wasPreviousFieldBoolean = isNowBoolean; - this.colorMappingChange.emit(this.colorMapping); - this.viewRefreshEmitter.emit(); + this.wasPreviousFieldBoolean = this.isBooleanPropertySelected(); + setTimeout(() => { + this.colorMappingChange.emit(this.colorMapping); + this.viewRefreshEmitter.emit(); + }); } addMapping() { @@ -138,6 +140,7 @@ export class ColorMappingOptionsConfigComponent implements OnInit, OnChanges { setCustomColorMapping(showCustomColorMapping: boolean) { this.showCustomColorMapping = showCustomColorMapping; + this.showCustomColorMappingChange.emit(showCustomColorMapping); if (!showCustomColorMapping) { this.resetColorMappings(); diff --git a/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.html b/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.html index 2fc82d5913..7d02b690c2 100644 --- a/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.html +++ b/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.html @@ -97,6 +97,10 @@ currentlyConfiguredWidget.visualizationConfig .selectedProperty " + [(showCustomColorMapping)]=" + currentlyConfiguredWidget.visualizationConfig + .showCustomColorMappingPieChart + " (viewRefreshEmitter)="triggerViewUpdate()" > </sp-color-mapping-options-config> diff --git a/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.ts b/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.ts index 7404a0c04d..e5f39247c7 100644 --- a/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.ts +++ b/ui/src/app/data-explorer-shared/components/charts/pie/config/pie-chart-widget-config.component.ts @@ -44,7 +44,7 @@ export class SpPieChartWidgetConfigComponent extends BaseWidgetConfig< setSelectedProperty(field: DataExplorerField) { this.currentlyConfiguredWidget.visualizationConfig.selectedProperty = field; - this.triggerDataRefresh(); + this.triggerViewRefresh(); } protected applyWidgetConfig(config: PieChartVisConfig): void { diff --git a/ui/src/app/data-explorer-shared/components/charts/pie/model/pie-chart-widget.model.ts b/ui/src/app/data-explorer-shared/components/charts/pie/model/pie-chart-widget.model.ts index 2dfbe624db..59a22a0245 100644 --- a/ui/src/app/data-explorer-shared/components/charts/pie/model/pie-chart-widget.model.ts +++ b/ui/src/app/data-explorer-shared/components/charts/pie/model/pie-chart-widget.model.ts @@ -27,7 +27,7 @@ export interface PieChartVisConfig extends DataExplorerVisConfig { selectedProperty: DataExplorerField; roundingValue: number; selectedRadius: number; - showCustomColorMapping: boolean; + showCustomColorMappingPieChart: boolean; isSelectedPropertyBoolean: boolean; colorMappingsPieChart: { value: string; label: string; color: string }[]; } diff --git a/ui/src/app/data-explorer-shared/components/charts/pie/pie-renderer.service.ts b/ui/src/app/data-explorer-shared/components/charts/pie/pie-renderer.service.ts index 4428968c0e..abf7821794 100644 --- a/ui/src/app/data-explorer-shared/components/charts/pie/pie-renderer.service.ts +++ b/ui/src/app/data-explorer-shared/components/charts/pie/pie-renderer.service.ts @@ -19,7 +19,7 @@ import { EChartsOption, PieSeriesOption } from 'echarts'; import { DataTransformOption } from 'echarts/types/src/data/helper/transform'; import { SpBaseSingleFieldEchartsRenderer } from '../../../echarts-renderer/base-single-field-echarts-renderer'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { PieChartWidgetModel } from './model/pie-chart-widget.model'; import { FieldUpdateInfo } from '../../../models/field-update.model'; import { ZRColor } from 'echarts/types/dist/shared'; @@ -30,9 +30,7 @@ export class SpPieRendererService extends SpBaseSingleFieldEchartsRenderer< PieChartWidgetModel, PieSeriesOption > { - constructor(private colorMappingService: ColorMappingService) { - super(); - } + colorMappingService = inject(ColorMappingService); addDatasetTransform( widgetConfig: PieChartWidgetModel, @@ -121,13 +119,12 @@ export class SpPieRendererService extends SpBaseSingleFieldEchartsRenderer< itemStyle: { color: params => { const category = params.data[0]; - const color = - colorMapping.find(c => c.value === category.toString()) - ?.color || + return (colorMapping.find( + c => c.value === category.toString(), + )?.color || this.colorMappingService.getDefaultColor( - params.dataIndex, - ); - return color as ZRColor; + params.data[0], + )) as ZRColor; }, }, }; diff --git a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.html b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.html index ea6cec6e5a..f400c83a8a 100644 --- a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.html +++ b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.html @@ -41,6 +41,10 @@ [selectedProperty]=" currentlyConfiguredWidget.visualizationConfig.selectedProperty " + [(showCustomColorMapping)]=" + currentlyConfiguredWidget.visualizationConfig + .showCustomColorMappingStatusHeatmap + " (viewRefreshEmitter)="triggerViewUpdate()" > </sp-color-mapping-options-config> diff --git a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.ts b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.ts index 96dc0f1d31..f0ae41830d 100644 --- a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.ts +++ b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.ts @@ -44,7 +44,7 @@ export class StatusHeatmapWidgetConfigComponent extends BaseWidgetConfig< setSelectedProperty(field: DataExplorerField) { this.currentlyConfiguredWidget.visualizationConfig.selectedProperty = field; - this.triggerDataRefresh(); + this.triggerViewRefresh(); } protected applyWidgetConfig(config: StatusHeatmapVisConfig): void { @@ -62,7 +62,7 @@ export class StatusHeatmapWidgetConfigComponent extends BaseWidgetConfig< triggerViewUpdate() { this.widgetConfigurationService.notify({ refreshView: true, - refreshData: true, + refreshData: false, }); } } diff --git a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/model/status-heatmap-widget.model.ts b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/model/status-heatmap-widget.model.ts index 719633bebf..a2de46fe73 100644 --- a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/model/status-heatmap-widget.model.ts +++ b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/model/status-heatmap-widget.model.ts @@ -26,7 +26,7 @@ import { DataExplorerVisConfig } from '../../../../models/dataview-dashboard.mod export interface StatusHeatmapVisConfig extends DataExplorerVisConfig { selectedProperty: DataExplorerField; isSelectedPropertyBoolean: boolean; - showCustomColorMapping: boolean; + showCustomColorMappingStatusHeatmap: boolean; colorMappingsStatusHeatmap: { value: string; label: string; diff --git a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/status-heatmap-renderer.service.ts b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/status-heatmap-renderer.service.ts index 1c35910d37..73d26543b4 100644 --- a/ui/src/app/data-explorer-shared/components/charts/status-heatmap/status-heatmap-renderer.service.ts +++ b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/status-heatmap-renderer.service.ts @@ -25,22 +25,20 @@ import { OptionDataValue, OptionSourceDataArrayRows, } from 'echarts/types/src/util/types'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { FieldUpdateInfo } from '../../../models/field-update.model'; import { ColorMappingService } from '../../../services/color-mapping.service'; @Injectable({ providedIn: 'root' }) export class SpStatusHeatmapRendererService extends SpBaseEchartsRenderer<StatusHeatmapWidgetModel> { - constructor(private colorMappingService: ColorMappingService) { - super(); - } + colorMappingService = inject(ColorMappingService); + applyOptions( generatedDataset: GeneratedDataset, options: EChartsOption, widgetConfig: StatusHeatmapWidgetModel, ): void { this.basicOptions(options); - const field = widgetConfig.visualizationConfig.selectedProperty; const sourceIndex = field.sourceIndex; @@ -63,91 +61,91 @@ export class SpStatusHeatmapRendererService extends SpBaseEchartsRenderer<Status return new Date(a[0]).getTime() - new Date(b[0]).getTime(); }); - const uniqueValues = [ - ...new Set(rawDatasetSource.map(row => row[statusIndex])), - ]; - const valueMapping = new Map( - uniqueValues.map((val, index) => [val, index]), - ); - - const transformedDataset = rawDatasetSource.map((row, index) => { - let statusValue = row[statusIndex]; - - if (typeof statusValue === 'boolean') { - statusValue = statusValue ? 1 : 0; - } else if ( - typeof statusValue === 'string' || - typeof statusValue === 'number' - ) { - statusValue = valueMapping.get(statusValue) ?? null; - } - - return [ - index, - this.makeTag(rawDataset.rawDataset.dimensions, tags, row), - statusValue, - ]; - }); - - options.dataset = { source: transformedDataset }; - - (options.xAxis as any).data = rawDatasetSource.map(s => { - return new Date(s[0]).toLocaleString(); - }); - - options.tooltip = { - formatter: params => { - const timestamp = rawDatasetSource[params.data[0]][0]; - const statusValue = params.value[2]; - const originalValue = uniqueValues[statusValue]; - - const statusLabel = - colorMapping.find(c => c.value === originalValue.toString()) - ?.label || originalValue; - - return `${params.marker} ${new Date(timestamp).toLocaleString()}<br/>Status: <b>${statusLabel}</b>`; - }, - }; - - const dynamicPieces = uniqueValues.map((val, index) => ({ - value: index, - label: - colorMapping.find(c => c.value === val.toString())?.label || - val.toString(), - color: - colorMapping.find(c => c.value === val.toString())?.color || - this.colorMappingService.getDefaultColor(index), - })); - - options.visualMap = { - type: 'piecewise', - pieces: dynamicPieces, - orient: 'horizontal', - right: '5%', - top: '20', - }; - - options.legend = { - type: 'scroll', - }; - - options.series = [ - { - name: '', - type: 'heatmap', - datasetIndex: 0, - encode: { - itemId: 0, - value: 2, + if (statusIndex >= 0) { + const mappedRawDataset = rawDatasetSource?.map( + row => row[statusIndex], + ); + const uniqueValuesSet = + mappedRawDataset !== undefined + ? new Set(mappedRawDataset) + : new Set(); + const uniqueValues = [...uniqueValuesSet]; + const uniqueValueSorted = uniqueValues.sort(); + + const valueMapping = new Map( + uniqueValueSorted.map((val, index) => [val, index]), + ); + + const transformedDataset = rawDatasetSource.map((row, index) => { + const statusValue = valueMapping.get(row[statusIndex]) ?? null; + return [ + index, + this.makeTag(rawDataset.rawDataset.dimensions, tags, row), + statusValue, + ]; + }); + + options.dataset = { source: transformedDataset }; + + (options.xAxis as any).data = rawDatasetSource.map(s => { + return new Date(s[0]).toLocaleString(); + }); + + options.tooltip = { + formatter: params => { + const timestamp = rawDatasetSource[params.data[0]][0]; + const statusValue = params.value[2]; + const originalValue = uniqueValueSorted[statusValue]; + + const statusLabel = + colorMapping.find( + c => c.value === originalValue.toString(), + )?.label || originalValue.toString(); + + return `${params.marker} ${new Date(timestamp).toLocaleString()}<br/>Status: <b>${statusLabel}</b>`; }, - emphasis: { - itemStyle: { - shadowBlur: 10, - shadowColor: 'rgba(0, 0, 0, 0.5)', + }; + + const dynamicPieces = uniqueValueSorted.map((val, index) => ({ + value: index, + label: + colorMapping.find(c => c.value === val.toString())?.label || + val.toString(), + color: + colorMapping.find(c => c.value === val.toString())?.color || + this.colorMappingService.getDefaultColor(val.toString()), + })); + + options.visualMap = { + type: 'piecewise', + pieces: dynamicPieces, + orient: 'horizontal', + right: '5%', + top: '20', + }; + + options.legend = { + type: 'scroll', + }; + + options.series = [ + { + name: '', + type: 'heatmap', + datasetIndex: 0, + encode: { + itemId: 0, + value: 2, + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, }, }, - }, - ]; + ]; + } } public handleUpdatedFields( diff --git a/ui/src/app/data-explorer-shared/services/color-mapping.service.ts b/ui/src/app/data-explorer-shared/services/color-mapping.service.ts index 73cc9d6dcc..e63b995160 100644 --- a/ui/src/app/data-explorer-shared/services/color-mapping.service.ts +++ b/ui/src/app/data-explorer-shared/services/color-mapping.service.ts @@ -22,17 +22,6 @@ import { Injectable } from '@angular/core'; providedIn: 'root', }) export class ColorMappingService { - private colorPalette = [ - '#5470c6', - '#91cc75', - '#fac858', - '#ee6666', - '#73c0de', - '#3ba272', - '#fc8452', - '#9a60b4', - '#ea7ccc', - ]; constructor() {} addMapping( @@ -41,7 +30,7 @@ export class ColorMappingService { colorMappings.push({ value: '', label: '', - color: this.getDefaultColor(colorMappings.length), + color: this.getDefaultColor(Math.random() * 1000), }); } @@ -60,7 +49,20 @@ export class ColorMappingService { currentMappings[index].color = newColor; } - getDefaultColor(index: number): string { - return this.colorPalette[index % this.colorPalette.length]; + getDefaultColor(value: string | number): string { + let hash = 0x811c9dc5; + const input = String(value); + + for (let i = 0; i < input.length; i++) { + hash ^= input.charCodeAt(i); + hash = (hash * 0x5bd1e995) & 0xffffffff; + hash ^= hash >> 15; + } + + const hue = Math.abs(hash) % 360; + const saturation = 50 + (Math.abs(hash >> 8) % 20); + const lightness = 45 + (Math.abs(hash >> 16) % 20); + + return `hsl(${hue}, ${saturation}%, ${lightness}%)`; } }
