This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new 23396697cc Add status heatmap and create color mapping component
(#3516)
23396697cc is described below
commit 23396697cc2680aa0a21bd22251a25366d6d6628
Author: Marcel Früholz <[email protected]>
AuthorDate: Fri Mar 7 09:00:41 2025 +0100
Add status heatmap and create color mapping component (#3516)
---
.../color-mapping-options-config.component.html | 154 +++++++++++++++++
.../color-mapping-options-config.component.ts | 148 ++++++++++++++++
.../config/pie-chart-widget-config.component.html | 93 +---------
.../config/pie-chart-widget-config.component.ts | 59 +------
.../charts/pie/model/pie-chart-widget.model.ts | 3 +-
.../components/charts/pie/pie-renderer.service.ts | 39 ++++-
.../status-heatmap-widget-config.component.html | 48 ++++++
.../status-heatmap-widget-config.component.ts | 68 ++++++++
.../model/status-heatmap-widget.model.ts} | 15 +-
.../status-heatmap-renderer.service.ts | 192 +++++++++++++++++++++
.../data-explorer-shared.module.ts | 4 +
.../base-single-field-echarts-renderer.ts | 16 +-
.../registry/data-explorer-chart-registry.ts | 15 ++
.../services/color-mapping.service.ts | 11 +-
14 files changed, 709 insertions(+), 156 deletions(-)
diff --git
a/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.html
b/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.html
new file mode 100644
index 0000000000..5ed6cc3235
--- /dev/null
+++
b/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.html
@@ -0,0 +1,154 @@
+<!--
+ ~ 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 fxLayout="column" fxLayoutGap="10px">
+ <div
+ fxLayout="row"
+ fxLayoutGap="10px"
+ fxLayoutAlign="start center"
+ fxFlex="1"
+ class="checkbox-container"
+ >
+ <mat-checkbox
+ color="accent"
+ [(ngModel)]="showCustomColorMapping"
+ (ngModelChange)="setCustomColorMapping($event)"
+ >
+ </mat-checkbox>
+ <small>{{ 'Add custom color mapping' | translate }}</small>
+ </div>
+
+ <div *ngIf="!isSelectedPropertyBoolean && showCustomColorMapping">
+ <div style="margin-bottom: 20px">
+ <button mat-raised-button color="accent" (click)="addMapping()">
+ <i class="material-icons">add</i>
+ <span> {{ 'Add Mapping' | translate }}</span>
+ </button>
+ </div>
+
+ <div fxLayout="column" fxLayoutGap="10px">
+ <div
+ *ngFor="let mapping of colorMapping; let i = index"
+ fxLayout="row"
+ fxLayoutGap="10px"
+ fxLayoutAlign="start center"
+ fxFlex="1"
+ style="margin-top: 10px; align-items: center"
+ >
+ <div fxFlex="150px">
+ <mat-form-field
+ class="w-100"
+ color="accent"
+ appearance="outline"
+ >
+ <mat-label>{{ 'Value' | translate }}</mat-label>
+ <input
+ matInput
+ [(ngModel)]="mapping.value"
+ (ngModelChange)="updateMapping()"
+ />
+ </mat-form-field>
+ </div>
+ <div fxFlex="150px">
+ <mat-form-field
+ class="w-100"
+ color="accent"
+ appearance="outline"
+ >
+ <mat-label>{{ 'Label' | translate }}</mat-label>
+ <input
+ matInput
+ [(ngModel)]="mapping.label"
+ (ngModelChange)="updateMapping()"
+ />
+ </mat-form-field>
+ </div>
+ <div fxFlex="40px">
+ <input
+ [(colorPicker)]="mapping.color"
+ [style.background]="mapping.color"
+ style="
+ height: 50%;
+ width: 100%;
+ border: none;
+ border-radius: 10%;
+ cursor: pointer;
+ "
+ (colorPickerChange)="updateColor(i, $event)"
+ />
+ </div>
+ <div fxLayoutAlign="end center">
+ <button
+ mat-icon-button
+ [matTooltip]="'Remove Mapping' | translate"
+ color="accent"
+ (click)="removeMapping(i)"
+ >
+ <i class="material-icons">delete</i>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div *ngIf="isSelectedPropertyBoolean && showCustomColorMapping">
+ <div
+ fxLayout="column"
+ fxLayoutGap="10px"
+ style="margin-top: 10px; margin-right: 10px"
+ >
+ <div
+ *ngFor="let mapping of colorMapping; let i = index"
+ fxLayout="row"
+ fxLayoutGap="10px"
+ fxLayoutAlign="start center"
+ fxFlex="1"
+ style="margin-top: 10px; align-items: center"
+ >
+ <div fxFlex="40px">{{ mapping.value }}</div>
+ <div fxFlex>
+ <mat-form-field
+ class="w-100"
+ color="accent"
+ appearance="outline"
+ >
+ <mat-label>Label</mat-label>
+ <input
+ matInput
+ [(ngModel)]="mapping.label"
+ (ngModelChange)="updateMapping()"
+ />
+ </mat-form-field>
+ </div>
+ <div fxFlex="70px">
+ <input
+ [(colorPicker)]="mapping.color"
+ [style.background]="mapping.color"
+ style="
+ height: 50%;
+ width: 100%;
+ border: none;
+ border-radius: 10%;
+ cursor: pointer;
+ "
+ (colorPickerChange)="updateColor(i, $event)"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
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
new file mode 100644
index 0000000000..f31dececc7
--- /dev/null
+++
b/ui/src/app/data-explorer-shared/components/chart-config/color-mapping-options-config/color-mapping-options-config.component.ts
@@ -0,0 +1,148 @@
+/*
+ * 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,
+ Output,
+ EventEmitter,
+ OnInit,
+ OnChanges,
+ SimpleChanges,
+} from '@angular/core';
+import { ColorMappingService } from '../../../services/color-mapping.service';
+import { DataExplorerField } from '@streampipes/platform-services';
+
+@Component({
+ selector: 'sp-color-mapping-options-config',
+ templateUrl: './color-mapping-options-config.component.html',
+})
+export class ColorMappingOptionsConfigComponent implements OnInit, OnChanges {
+ @Input() colorMapping: { value: string; label: string; color: string }[];
+
+ @Input() selectedProperty: DataExplorerField;
+
+ @Output()
+ viewRefreshEmitter: EventEmitter<void> = new EventEmitter<void>();
+
+ @Output()
+ colorMappingChange: EventEmitter<
+ { value: string; label: string; color: string }[]
+ > = new EventEmitter();
+
+ protected isSelectedPropertyBoolean: boolean;
+ protected showCustomColorMapping: boolean;
+ private wasPreviousFieldBoolean: boolean;
+
+ constructor(private colorMappingService: ColorMappingService) {}
+
+ ngOnInit(): void {
+ this.isSelectedPropertyBoolean = this.isBooleanPropertySelected();
+ this.showCustomColorMapping ??= false;
+ this.resetColorMappings();
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (
+ changes['selectedProperty'] &&
+ !changes['selectedProperty'].firstChange
+ ) {
+ this.resetColorMappings();
+ this.isSelectedPropertyBoolean = this.isBooleanPropertySelected();
+ }
+ }
+
+ resetColorMappings(): void {
+ const isNowBoolean = this.isBooleanPropertySelected();
+
+ if (!this.showCustomColorMapping) {
+ if (isNowBoolean) {
+ this.colorMapping = [
+ { value: 'true', label: '', color: '#66BB66' },
+ { value: 'false', label: '', color: '#BB6666' },
+ ];
+ } else {
+ this.colorMapping = [];
+ }
+ }
+ if (isNowBoolean) {
+ if (
+ !(this.colorMapping ?? []).some(
+ mapping =>
+ mapping.value === 'true' || mapping.value === 'false',
+ )
+ ) {
+ this.colorMapping = [
+ { value: 'true', label: '', color: '#66BB66' },
+ { value: 'false', label: '', color: '#BB6666' },
+ ];
+ }
+ } else {
+ if (this.wasPreviousFieldBoolean) {
+ this.colorMapping = [];
+ }
+ }
+ this.wasPreviousFieldBoolean = isNowBoolean;
+ this.colorMappingChange.emit(this.colorMapping);
+ this.viewRefreshEmitter.emit();
+ }
+
+ addMapping() {
+ this.colorMappingService.addMapping(this.colorMapping);
+ this.colorMappingChange.emit(this.colorMapping);
+ this.viewRefreshEmitter.emit();
+ }
+
+ removeMapping(index: number) {
+ this.colorMapping = this.colorMappingService.removeMapping(
+ this.colorMapping,
+ index,
+ );
+ this.colorMappingChange.emit(this.colorMapping);
+ this.viewRefreshEmitter.emit();
+ }
+
+ updateColor(index: number, newColor: string) {
+ this.colorMappingService.updateColor(
+ this.colorMapping,
+ index,
+ newColor,
+ );
+ this.colorMappingChange.emit(this.colorMapping);
+ this.viewRefreshEmitter.emit();
+ }
+
+ updateMapping() {
+ this.colorMappingChange.emit(this.colorMapping);
+ this.viewRefreshEmitter.emit();
+ }
+
+ isBooleanPropertySelected(): boolean {
+ return this.selectedProperty.fieldCharacteristics.binary;
+ }
+
+ setCustomColorMapping(showCustomColorMapping: boolean) {
+ this.showCustomColorMapping = showCustomColorMapping;
+
+ if (!showCustomColorMapping) {
+ this.resetColorMappings();
+ }
+
+ this.viewRefreshEmitter.emit();
+ }
+}
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 f0fa2e4eef..2fc82d5913 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
@@ -87,98 +87,19 @@
</mat-slider>
<small>{{ slider.value }}% </small>
</div>
- <div
- fxLayout="row"
- fxLayoutGap="10px"
- fxLayoutAlign="start center"
- fxFlex="100"
- class="checkbox-container"
- >
- <mat-checkbox
- color="accent"
- [(ngModel)]="
- currentlyConfiguredWidget.visualizationConfig
- .showCustomColorMapping
- "
- (ngModelChange)="showCustomColorMapping($event)"
- >
- </mat-checkbox>
- <small>{{ 'Add custom color mapping' | translate }}</small>
- </div>
- <div
- *ngIf="
+ <sp-color-mapping-options-config
+ [(colorMapping)]="
currentlyConfiguredWidget.visualizationConfig
- .showCustomColorMapping
+ .colorMappingsPieChart
"
- >
- <button mat-raised-button color="accent"
(click)="addMapping()">
- <i class="material-icons">add</i
- ><span> {{ 'Add Mapping' | translate }}</span>
- </button>
- </div>
-
- <div
- *ngIf="
+ [selectedProperty]="
currentlyConfiguredWidget.visualizationConfig
- .showCustomColorMapping
+ .selectedProperty
"
+ (viewRefreshEmitter)="triggerViewUpdate()"
>
- <div fxLayout="column" fxLayoutGap="10px">
- <div
- *ngFor="
- let mapping of currentlyConfiguredWidget
- .visualizationConfig.colorMappings;
- let i = index
- "
- fxLayout="row"
- fxLayoutGap="10px"
- fxLayoutAlign="start center"
- fxFlex="100"
- style="margin-top: 10px; align-items: center"
- >
- <div fxFlex>
- <mat-form-field
- class="w-100"
- color="accent"
- appearance="outline"
- >
- <mat-label>{{ 'Value' | translate
}}</mat-label>
- <input
- matInput
- [(ngModel)]="mapping.value"
- (ngModelChange)="updateMapping()"
- />
- </mat-form-field>
- </div>
- <div fxFlex="70px">
- <input
- [(colorPicker)]="mapping.color"
- [style.background]="mapping.color"
- style="
- height: 50%;
- width: 100%;
- border: none;
- border-radius: 10%;
- cursor: pointer;
- "
- (colorPickerChange)="updateColor(i, $event)"
- readonly
- />
- </div>
- <div fxLayoutAlign="end center">
- <button
- mat-icon-button
- [matTooltip]="'Remove Mapping' | translate"
- color="accent"
- (click)="removeMapping(i)"
- >
- <i class="material-icons">delete</i>
- </button>
- </div>
- </div>
- </div>
- </div>
+ </sp-color-mapping-options-config>
</div>
</sp-configuration-box>
</sp-visualization-config-outer>
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 63d7c4ebbe..7404a0c04d 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
@@ -23,7 +23,6 @@ import {
PieChartWidgetModel,
} from '../model/pie-chart-widget.model';
import { DataExplorerField } from '@streampipes/platform-services';
-import { ColorMappingService } from
'../../../../services/color-mapping.service';
import { ChartConfigurationService } from
'../../../../services/chart-configuration.service';
import { DataExplorerFieldProviderService } from
'../../../../services/data-explorer-field-provider-service';
@@ -36,7 +35,6 @@ export class SpPieChartWidgetConfigComponent extends
BaseWidgetConfig<
PieChartVisConfig
> {
constructor(
- private colorMappingService: ColorMappingService,
widgetConfigurationService: ChartConfigurationService,
fieldService: DataExplorerFieldProviderService,
) {
@@ -46,7 +44,7 @@ export class SpPieChartWidgetConfigComponent extends
BaseWidgetConfig<
setSelectedProperty(field: DataExplorerField) {
this.currentlyConfiguredWidget.visualizationConfig.selectedProperty =
field;
- this.triggerViewRefresh();
+ this.triggerDataRefresh();
}
protected applyWidgetConfig(config: PieChartVisConfig): void {
@@ -57,8 +55,6 @@ export class SpPieChartWidgetConfigComponent extends
BaseWidgetConfig<
);
config.roundingValue ??= 0.1;
config.selectedRadius ??= 0;
- config.showCustomColorMapping ??= false;
- config.colorMappings ??= [];
}
updateRoundingValue(selectedType: number) {
@@ -73,53 +69,14 @@ export class SpPieChartWidgetConfigComponent extends
BaseWidgetConfig<
this.triggerViewRefresh();
}
- showCustomColorMapping(showCustomColorMapping: boolean) {
-
this.currentlyConfiguredWidget.visualizationConfig.showCustomColorMapping =
- showCustomColorMapping;
-
- if (!showCustomColorMapping) {
- this.resetColorMappings();
- }
-
- this.triggerViewRefresh();
- }
-
- resetColorMappings(): void {
- this.currentlyConfiguredWidget.visualizationConfig.colorMappings = [];
- this.triggerViewRefresh();
- }
-
- addMapping() {
- this.colorMappingService.addMapping(
- this.currentlyConfiguredWidget.visualizationConfig.colorMappings,
- );
- this.triggerViewRefresh();
- }
-
- removeMapping(index: number) {
- this.currentlyConfiguredWidget.visualizationConfig.colorMappings =
- this.colorMappingService.removeMapping(
- this.currentlyConfiguredWidget.visualizationConfig
- .colorMappings,
- index,
- );
- this.triggerViewRefresh();
- }
-
- updateColor(index: number, newColor: string) {
- this.colorMappingService.updateColor(
- this.currentlyConfiguredWidget.visualizationConfig.colorMappings,
- index,
- newColor,
- );
- this.triggerViewRefresh();
- }
-
- updateMapping() {
- this.triggerViewRefresh();
- }
-
protected requiredFieldsForChartPresent(): boolean {
return this.fieldProvider.allFields.length > 0;
}
+
+ triggerViewUpdate() {
+ this.widgetConfigurationService.notify({
+ refreshView: true,
+ refreshData: false,
+ });
+ }
}
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 ad7339776e..2dfbe624db 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
@@ -28,7 +28,8 @@ export interface PieChartVisConfig extends
DataExplorerVisConfig {
roundingValue: number;
selectedRadius: number;
showCustomColorMapping: boolean;
- colorMappings: { value: string; color: string }[];
+ isSelectedPropertyBoolean: boolean;
+ colorMappingsPieChart: { value: string; label: string; color: string }[];
}
export interface PieChartWidgetModel extends DataExplorerWidgetModel {
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 20a74dea05..4428968c0e 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
@@ -61,8 +61,27 @@ export class SpPieRendererService extends
SpBaseSingleFieldEchartsRenderer<
);
}
- addAdditionalConfigs(option: EChartsOption) {
- // do nothing
+ addAdditionalConfigs(
+ option: EChartsOption,
+ widgetConfig: PieChartWidgetModel,
+ ): void {
+ if (
+ widgetConfig.visualizationConfig.selectedProperty
+ .fieldCharacteristics.binary
+ ) {
+ option.legend = { show: false };
+ } else {
+ option.legend = {
+ type: 'scroll',
+ formatter: name => {
+ return (
+
widgetConfig.visualizationConfig.colorMappingsPieChart.find(
+ c => String(c.value) === name,
+ )?.label || name
+ );
+ },
+ };
+ }
}
addSeriesItem(
@@ -71,7 +90,9 @@ export class SpPieRendererService extends
SpBaseSingleFieldEchartsRenderer<
_widgetConfig: PieChartWidgetModel,
): PieSeriesOption {
const innerRadius = _widgetConfig.visualizationConfig.selectedRadius;
- const colorMapping = _widgetConfig.visualizationConfig.colorMappings;
+ const colorMapping =
+ _widgetConfig.visualizationConfig.colorMappingsPieChart;
+
return {
name,
type: 'pie',
@@ -79,12 +100,20 @@ export class SpPieRendererService extends
SpBaseSingleFieldEchartsRenderer<
datasetIndex: datasetIndex,
tooltip: {
formatter: params => {
- return `${params.marker} ${params.value[0]}
<b>${params.value[1]}</b> (${params.percent}%)`;
+ const mappedLabel =
+ colorMapping.find(
+ c => c.value === params.value[0]?.toString(),
+ )?.label || params.value[0];
+ return `${params.marker} ${mappedLabel}
<b>${params.value[1]}</b> (${params.percent}%)`;
},
},
label: {
formatter: params => {
- return `${params.value[0]} (${params.percent}%)`;
+ const mappedLabel =
+ colorMapping.find(
+ c => c.value === params.value[0]?.toString(),
+ )?.label || params.value[0];
+ return `${mappedLabel} (${params.percent}%)`;
},
},
encode: { itemName: 'name', value: 'value' },
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
new file mode 100644
index 0000000000..ea6cec6e5a
--- /dev/null
+++
b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.html
@@ -0,0 +1,48 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<sp-visualization-config-outer
+ [configurationValid]="
+ currentlyConfiguredWidget.visualizationConfig.configurationValid
+ "
+>
+ <sp-configuration-box [title]="'Field' | translate">
+ <sp-select-single-property-config
+ [availableProperties]="fieldProvider.allFields"
+ [selectedProperty]="
+ currentlyConfiguredWidget.visualizationConfig.selectedProperty
+ "
+ (changeSelectedProperty)="setSelectedProperty($event)"
+ >
+ </sp-select-single-property-config>
+ </sp-configuration-box>
+
+ <sp-configuration-box [title]="'Settings' | translate">
+ <sp-color-mapping-options-config
+ [(colorMapping)]="
+ currentlyConfiguredWidget.visualizationConfig
+ .colorMappingsStatusHeatmap
+ "
+ [selectedProperty]="
+ currentlyConfiguredWidget.visualizationConfig.selectedProperty
+ "
+ (viewRefreshEmitter)="triggerViewUpdate()"
+ >
+ </sp-color-mapping-options-config>
+ </sp-configuration-box>
+</sp-visualization-config-outer>
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
new file mode 100644
index 0000000000..96dc0f1d31
--- /dev/null
+++
b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/config/status-heatmap-widget-config.component.ts
@@ -0,0 +1,68 @@
+/*
+ * 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 } from '@angular/core';
+import { BaseWidgetConfig } from '../../base/base-widget-config';
+import { ChartConfigurationService } from
'../../../../services/chart-configuration.service';
+import {
+ StatusHeatmapVisConfig,
+ StatusHeatmapWidgetModel,
+} from '../model/status-heatmap-widget.model';
+import { DataExplorerFieldProviderService } from
'../../../../services/data-explorer-field-provider-service';
+import { DataExplorerField } from '@streampipes/platform-services';
+
+@Component({
+ selector: 'sp-data-explorer-status-heatmap-widget-config',
+ templateUrl: './status-heatmap-widget-config.component.html',
+})
+export class StatusHeatmapWidgetConfigComponent extends BaseWidgetConfig<
+ StatusHeatmapWidgetModel,
+ StatusHeatmapVisConfig
+> {
+ constructor(
+ widgetConfigurationService: ChartConfigurationService,
+ fieldService: DataExplorerFieldProviderService,
+ ) {
+ super(widgetConfigurationService, fieldService);
+ }
+
+ setSelectedProperty(field: DataExplorerField) {
+ this.currentlyConfiguredWidget.visualizationConfig.selectedProperty =
+ field;
+ this.triggerDataRefresh();
+ }
+
+ protected applyWidgetConfig(config: StatusHeatmapVisConfig): void {
+ config.selectedProperty = this.fieldService.getSelectedField(
+ config.selectedProperty,
+ this.fieldProvider.allFields,
+ () => this.fieldProvider.allFields[0],
+ );
+ }
+
+ protected requiredFieldsForChartPresent(): boolean {
+ return this.fieldProvider.allFields.length > 0;
+ }
+
+ triggerViewUpdate() {
+ this.widgetConfigurationService.notify({
+ refreshView: true,
+ refreshData: true,
+ });
+ }
+}
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/status-heatmap/model/status-heatmap-widget.model.ts
similarity index 76%
copy from
ui/src/app/data-explorer-shared/components/charts/pie/model/pie-chart-widget.model.ts
copy to
ui/src/app/data-explorer-shared/components/charts/status-heatmap/model/status-heatmap-widget.model.ts
index ad7339776e..719633bebf 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/status-heatmap/model/status-heatmap-widget.model.ts
@@ -23,15 +23,18 @@ import {
} from '@streampipes/platform-services';
import { DataExplorerVisConfig } from
'../../../../models/dataview-dashboard.model';
-export interface PieChartVisConfig extends DataExplorerVisConfig {
+export interface StatusHeatmapVisConfig extends DataExplorerVisConfig {
selectedProperty: DataExplorerField;
- roundingValue: number;
- selectedRadius: number;
+ isSelectedPropertyBoolean: boolean;
showCustomColorMapping: boolean;
- colorMappings: { value: string; color: string }[];
+ colorMappingsStatusHeatmap: {
+ value: string;
+ label: string;
+ color: string;
+ }[];
}
-export interface PieChartWidgetModel extends DataExplorerWidgetModel {
+export interface StatusHeatmapWidgetModel extends DataExplorerWidgetModel {
dataConfig: DataExplorerDataConfig;
- visualizationConfig: PieChartVisConfig;
+ visualizationConfig: StatusHeatmapVisConfig;
}
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
new file mode 100644
index 0000000000..1c35910d37
--- /dev/null
+++
b/ui/src/app/data-explorer-shared/components/charts/status-heatmap/status-heatmap-renderer.service.ts
@@ -0,0 +1,192 @@
+/*
+ * 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
'../../../echarts-renderer/base-echarts-renderer';
+import { StatusHeatmapWidgetModel } from './model/status-heatmap-widget.model';
+import { GeneratedDataset, TagValue } from '../../../models/dataset.model';
+import { EChartsOption } from 'echarts';
+import {
+ DimensionDefinitionLoose,
+ OptionDataValue,
+ OptionSourceDataArrayRows,
+} from 'echarts/types/src/util/types';
+import { Injectable } 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();
+ }
+ applyOptions(
+ generatedDataset: GeneratedDataset,
+ options: EChartsOption,
+ widgetConfig: StatusHeatmapWidgetModel,
+ ): void {
+ this.basicOptions(options);
+
+ const field = widgetConfig.visualizationConfig.selectedProperty;
+ const sourceIndex = field.sourceIndex;
+
+ const rawDataset = this.datasetUtilsService.findPreparedDataset(
+ generatedDataset,
+ sourceIndex,
+ );
+ const rawDatasetSource: OptionSourceDataArrayRows = rawDataset
+ .rawDataset.source as OptionSourceDataArrayRows;
+ const tags = rawDataset.tagValues;
+ const statusIndex = rawDataset.rawDataset.dimensions.indexOf(
+ field.fullDbName,
+ );
+
+ const colorMapping =
+ widgetConfig.visualizationConfig.colorMappingsStatusHeatmap;
+
+ rawDatasetSource.shift();
+ rawDatasetSource.sort((a, b) => {
+ 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,
+ },
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
+ },
+ },
+ },
+ ];
+ }
+
+ public handleUpdatedFields(
+ fieldUpdateInfo: FieldUpdateInfo,
+ widgetConfig: StatusHeatmapWidgetModel,
+ ): void {
+ this.fieldUpdateService.updateAnyField(
+ widgetConfig.visualizationConfig.selectedProperty,
+ fieldUpdateInfo,
+ );
+ }
+
+ basicOptions(options: EChartsOption): void {
+ options.grid = {
+ height: '80%',
+ top: '80',
+ };
+ options.xAxis = {
+ type: 'category',
+ splitArea: { show: true },
+ };
+ options.yAxis = {
+ type: 'category',
+ splitArea: { show: true },
+ };
+ }
+
+ private makeTag(
+ dimensions: DimensionDefinitionLoose[],
+ tags: TagValue[],
+ row: Array<OptionDataValue>,
+ ) {
+ if (tags.length > 0) {
+ return tags[0].tagKeys
+ .map(key => {
+ const index = dimensions.indexOf(key);
+ return row[index];
+ })
+ .toString();
+ }
+ }
+}
diff --git a/ui/src/app/data-explorer-shared/data-explorer-shared.module.ts
b/ui/src/app/data-explorer-shared/data-explorer-shared.module.ts
index fa32f6acd5..562b5e5aa7 100644
--- a/ui/src/app/data-explorer-shared/data-explorer-shared.module.ts
+++ b/ui/src/app/data-explorer-shared/data-explorer-shared.module.ts
@@ -80,6 +80,7 @@ import { StatusWidgetConfigComponent } from
'./components/charts/status/config/s
import { MapWidgetConfigComponent } from
'./components/charts/map/config/map-widget-config.component';
import { MapWidgetComponent } from
'./components/charts/map/map-widget.component';
import { HeatmapWidgetConfigComponent } from
'./components/charts/heatmap/config/heatmap-widget-config.component';
+import { StatusHeatmapWidgetConfigComponent } from
'./components/charts/status-heatmap/config/status-heatmap-widget-config.component';
import { ImageViewerComponent } from
'./components/charts/image/image-viewer/image-viewer.component';
import { ChartDirective } from './components/chart-container/chart.directive';
import { TooMuchDataComponent } from
'./components/charts/base/too-much-data/too-much-data.component';
@@ -96,6 +97,7 @@ import { SpEchartsWidgetAppearanceConfigComponent } from
'./components/chart-con
import { SpTimeSeriesAppearanceConfigComponent } from
'./components/charts/time-series-chart/appearance-config/time-series-appearance-config.component';
import { SpDataZoomConfigComponent } from
'./components/chart-config/data-zoom-config/data-zoom-config.component';
import { TranslateModule } from '@ngx-translate/core';
+import { ColorMappingOptionsConfigComponent } from
'./components/chart-config/color-mapping-options-config/color-mapping-options-config.component';
@NgModule({
imports: [
@@ -164,6 +166,7 @@ import { TranslateModule } from '@ngx-translate/core';
TrafficLightWidgetConfigComponent,
StatusWidgetComponent,
StatusWidgetConfigComponent,
+ StatusHeatmapWidgetConfigComponent,
MapWidgetConfigComponent,
MapWidgetComponent,
HeatmapWidgetConfigComponent,
@@ -182,6 +185,7 @@ import { TranslateModule } from '@ngx-translate/core';
SpEchartsWidgetAppearanceConfigComponent,
SpTimeSeriesAppearanceConfigComponent,
SpDataZoomConfigComponent,
+ ColorMappingOptionsConfigComponent,
],
exports: [DataExplorerChartContainerComponent],
})
diff --git
a/ui/src/app/data-explorer-shared/echarts-renderer/base-single-field-echarts-renderer.ts
b/ui/src/app/data-explorer-shared/echarts-renderer/base-single-field-echarts-renderer.ts
index 2ceae6ad6b..f1f366b34e 100644
---
a/ui/src/app/data-explorer-shared/echarts-renderer/base-single-field-echarts-renderer.ts
+++
b/ui/src/app/data-explorer-shared/echarts-renderer/base-single-field-echarts-renderer.ts
@@ -73,7 +73,13 @@ export abstract class SpBaseSingleFieldEchartsRenderer<
gridOptions,
);
this.configureAxes(options, widgetConfig, numberOfCharts, series);
- this.finalizeOptions(options, datasets, series, gridOptions);
+ this.finalizeOptions(
+ options,
+ datasets,
+ series,
+ gridOptions,
+ widgetConfig,
+ );
}
private getNumberOfCharts(tags: TagValue[]): number {
@@ -163,6 +169,7 @@ export abstract class SpBaseSingleFieldEchartsRenderer<
dataset: DatasetOption[],
series: S[],
gridOptions: GridOptions,
+ widgetConfig: T,
) {
if (series.length > 1) {
this.echartsUtilsService.addSeriesTitles(
@@ -173,7 +180,7 @@ export abstract class SpBaseSingleFieldEchartsRenderer<
}
options.dataset = dataset;
options.series = series;
- this.addAdditionalConfigs(options);
+ this.addAdditionalConfigs(options, widgetConfig);
}
showAxes(): boolean {
@@ -182,7 +189,10 @@ export abstract class SpBaseSingleFieldEchartsRenderer<
abstract addDatasetTransform(widgetConfig: T): DataTransformOption;
- abstract addAdditionalConfigs(option: EChartsOption): void;
+ abstract addAdditionalConfigs(
+ option: EChartsOption,
+ widgetConfig?: T,
+ ): void;
abstract addSeriesItem(
name: string,
diff --git
a/ui/src/app/data-explorer-shared/registry/data-explorer-chart-registry.ts
b/ui/src/app/data-explorer-shared/registry/data-explorer-chart-registry.ts
index 9a25dc1e19..45ff00f2cb 100644
--- a/ui/src/app/data-explorer-shared/registry/data-explorer-chart-registry.ts
+++ b/ui/src/app/data-explorer-shared/registry/data-explorer-chart-registry.ts
@@ -23,6 +23,7 @@ import { TableWidgetComponent } from
'../components/charts/table/table-widget.co
import { MapWidgetConfigComponent } from
'../components/charts/map/config/map-widget-config.component';
import { MapWidgetComponent } from
'../components/charts/map/map-widget.component';
import { HeatmapWidgetConfigComponent } from
'../components/charts/heatmap/config/heatmap-widget-config.component';
+import { StatusHeatmapWidgetConfigComponent } from
'../components/charts/status-heatmap/config/status-heatmap-widget-config.component';
import { TimeSeriesChartWidgetConfigComponent } from
'../components/charts/time-series-chart/config/time-series-chart-widget-config.component';
import { ImageWidgetConfigComponent } from
'../components/charts/image/config/image-widget-config.component';
import { ImageWidgetComponent } from
'../components/charts/image/image-widget.component';
@@ -30,6 +31,7 @@ import { IndicatorWidgetConfigComponent } from
'../components/charts/indicator/c
import { CorrelationWidgetConfigComponent } from
'../components/charts/correlation-chart/config/correlation-chart-widget-config.component';
import { SpEchartsWidgetComponent } from
'../components/charts/base/echarts-widget.component';
import { HeatmapWidgetModel } from
'../components/charts/heatmap/model/heatmap-widget.model';
+import { StatusHeatmapWidgetModel } from
'../components/charts/status-heatmap/model/status-heatmap-widget.model';
import { SpValueHeatmapWidgetConfigComponent } from
'../components/charts/value-heatmap/config/value-heatmap-chart-widget-config.component';
import { SpHistogramChartWidgetConfigComponent } from
'../components/charts/histogram/config/histogram-chart-widget-config.component';
import { SpPieChartWidgetConfigComponent } from
'../components/charts/pie/config/pie-chart-widget-config.component';
@@ -38,6 +40,7 @@ import { PieChartWidgetModel } from
'../components/charts/pie/model/pie-chart-wi
import { ValueHeatmapChartWidgetModel } from
'../components/charts/value-heatmap/model/value-heatmap-chart-widget.model';
import { SpHistogramRendererService } from
'../components/charts/histogram/histogram-renderer.service';
import { SpHeatmapRendererService } from
'../components/charts/heatmap/heatmap-renderer.service';
+import { SpStatusHeatmapRendererService } from
'../components/charts/status-heatmap/status-heatmap-renderer.service';
import { SpPieRendererService } from
'../components/charts/pie/pie-renderer.service';
import { SpValueHeatmapRendererService } from
'../components/charts/value-heatmap/value-heatmap-renderer.service';
import { CorrelationChartWidgetModel } from
'../components/charts/correlation-chart/model/correlation-chart-widget.model';
@@ -65,6 +68,7 @@ export class DataExplorerChartRegistry {
constructor(
private gaugeRenderer: SpGaugeRendererService,
private heatmapRenderer: SpHeatmapRendererService,
+ private statusHeatmapRenderer: SpStatusHeatmapRendererService,
private histogramRenderer: SpHistogramRendererService,
private pieRenderer: SpPieRendererService,
private valueHeatmapRenderer: SpValueHeatmapRendererService,
@@ -117,6 +121,17 @@ export class DataExplorerChartRegistry {
widgetComponent: SpEchartsWidgetComponent<HeatmapWidgetModel>,
chartRenderer: this.heatmapRenderer,
},
+ {
+ id: 'status-heatmap',
+ label: 'Status Heatmap',
+ widgetAppearanceConfigurationComponent:
+ SpEchartsWidgetAppearanceConfigComponent,
+ widgetConfigurationComponent:
+ StatusHeatmapWidgetConfigComponent,
+ widgetComponent:
+ SpEchartsWidgetComponent<StatusHeatmapWidgetModel>,
+ chartRenderer: this.statusHeatmapRenderer,
+ },
{
id: 'time-series-chart',
label: this.translateService.instant('Time Series Chart'),
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 9b878a32d4..73cc9d6dcc 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
@@ -35,22 +35,25 @@ export class ColorMappingService {
];
constructor() {}
- addMapping(colorMappings: { value: string; color: string }[]): void {
+ addMapping(
+ colorMappings: { value: string; label: string; color: string }[],
+ ): void {
colorMappings.push({
value: '',
+ label: '',
color: this.getDefaultColor(colorMappings.length),
});
}
removeMapping(
- colorMappings: { value: string; color: string }[],
+ colorMappings: { value: string; label: string; color: string }[],
index: number,
- ): { value: string; color: string }[] {
+ ): { value: string; label: string; color: string }[] {
return colorMappings.filter((_, i) => i !== index);
}
updateColor(
- currentMappings: { value: string; color: string }[],
+ currentMappings: { value: string; label: string; color: string }[],
index: number,
newColor: string,
): void {