This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch 3090-add-gauge-chart-to-data-explorer
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit 8bd4ac21e1a8097337c377665084e0e1e0c68082
Author: Dominik Riemer <[email protected]>
AuthorDate: Sun Aug 11 22:41:58 2024 +0200

    feat(#3090): Add gauge chart to data explorer
---
 .../src/lib/apis/datalake-rest.service.ts          |   8 +-
 .../data-explorer-dashboard-panel.component.ts     |   1 +
 .../base/base-data-explorer-widget.directive.ts    |   2 -
 .../config/gauge-widget-config.component.html      | 100 +++++++++++++++++
 .../gauge/config/gauge-widget-config.component.ts  |  60 +++++++++++
 .../widgets/gauge/gauge-renderer.service.ts        | 118 +++++++++++++++++++++
 .../widgets/gauge/model/gauge-widget.model.ts      |  38 +++++++
 ui/src/app/data-explorer/data-explorer.module.ts   |   2 +
 .../registry/data-explorer-widget-registry.ts      |  13 +++
 9 files changed, 339 insertions(+), 3 deletions(-)

diff --git 
a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
 
b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
index 76f552ac3e..09535f38d3 100644
--- 
a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
+++ 
b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
@@ -17,11 +17,17 @@
  */
 
 import { Injectable } from '@angular/core';
-import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http';
+import {
+    HttpClient,
+    HttpContext,
+    HttpParams,
+    HttpRequest,
+} from '@angular/common/http';
 import { Observable, of } from 'rxjs';
 import { DataLakeMeasure, SpQueryResult } from 
'../model/gen/streampipes-model';
 import { map } from 'rxjs/operators';
 import { DatalakeQueryParameters } from 
'../model/datalake/DatalakeQueryParameters';
+import { NGX_LOADING_BAR_IGNORED } from '@ngx-loading-bar/http-client';
 
 @Injectable({
     providedIn: 'root',
diff --git 
a/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts
 
b/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts
index e932e8945a..8f2c7ad649 100644
--- 
a/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts
+++ 
b/ui/src/app/data-explorer/components/dashboard/data-explorer-dashboard-panel.component.ts
@@ -216,6 +216,7 @@ export class DataExplorerDashboardPanelComponent implements 
OnInit, OnDestroy {
                     ? this.overrideTime(+startTime, +endTime)
                     : this.dashboard.dashboardTimeSettings;
             this.dashboardLoaded = true;
+            this.modifyRefreshInterval();
         });
     }
 
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 beaea66509..b8e86ffdb9 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
@@ -147,7 +147,6 @@ export abstract class BaseDataExplorerWidgetDirective<
                 this.timerCallback.emit(false);
                 setTimeout(() => {
                     this.validateReceivedData(results);
-                    this.refreshView();
                 });
             });
 
@@ -234,7 +233,6 @@ export abstract class BaseDataExplorerWidgetDirective<
 
     public updateData(includeTooMuchEventsParameter: boolean = true) {
         this.beforeDataFetched();
-
         this.loadData(includeTooMuchEventsParameter);
     }
 
diff --git 
a/ui/src/app/data-explorer/components/widgets/gauge/config/gauge-widget-config.component.html
 
b/ui/src/app/data-explorer/components/widgets/gauge/config/gauge-widget-config.component.html
new file mode 100644
index 0000000000..f50603abd6
--- /dev/null
+++ 
b/ui/src/app/data-explorer/components/widgets/gauge/config/gauge-widget-config.component.html
@@ -0,0 +1,100 @@
+<!--
+  ~ 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">
+        <sp-select-property
+            [availableProperties]="fieldProvider.numericFields"
+            [selectedProperty]="
+                currentlyConfiguredWidget.visualizationConfig.selectedProperty
+            "
+            (changeSelectedProperty)="setSelectedProperty($event)"
+        >
+        </sp-select-property>
+    </sp-configuration-box>
+    <sp-configuration-box
+        title="Settings"
+        *ngIf="
+            currentlyConfiguredWidget.visualizationConfig.selectedProperty
+                ?.fieldCharacteristics.numeric
+        "
+    >
+        <div fxLayout="column">
+            <div fxFlex fxLayoutAlign="start center" class="ml-10 mb-10">
+                <small>Min</small>
+                <span fxFlex></span>
+                <mat-form-field appearance="outline" color="accent" 
fxFlex="30">
+                    <input
+                        matInput
+                        type="number"
+                        [(ngModel)]="
+                            currentlyConfiguredWidget.visualizationConfig.min
+                        "
+                        (ngModelChange)="triggerViewRefresh()"
+                    />
+                </mat-form-field>
+            </div>
+            <div fxFlex fxLayoutAlign="start center" class="ml-10 mb-10">
+                <small>Max</small>
+                <span fxFlex></span>
+                <mat-form-field appearance="outline" color="accent" 
fxFlex="30">
+                    <input
+                        matInput
+                        type="number"
+                        [(ngModel)]="
+                            currentlyConfiguredWidget.visualizationConfig.max
+                        "
+                        (ngModelChange)="triggerViewRefresh()"
+                    />
+                </mat-form-field>
+            </div>
+        </div>
+        <!--    <div-->
+        <!--        fxFlex="100"-->
+        <!--        fxLayout="row"-->
+        <!--        fxLayoutAlign="start center"-->
+        <!--        fxLayoutGap="10px"-->
+        <!--    >-->
+        <!--      <small>Rounding</small>-->
+        <!--      <mat-form-field-->
+        <!--          appearance="outline"-->
+        <!--          class="marginColorField"-->
+        <!--          color="accent"-->
+        <!--          fxFlex-->
+        <!--      >-->
+        <!--        <mat-select-->
+        <!--            [(value)]="-->
+        <!--                        
currentlyConfiguredWidget.visualizationConfig-->
+        <!--                            .roundingValue-->
+        <!--                    "-->
+        <!--            
(selectionChange)="updateRoundingValue($event.value)"-->
+        <!--        >-->
+        <!--          <mat-option [value]="100">100</mat-option>-->
+        <!--          <mat-option [value]="10">10</mat-option>-->
+        <!--          <mat-option [value]="1">1</mat-option>-->
+        <!--          <mat-option [value]="0.1">0.1</mat-option>-->
+        <!--          <mat-option [value]="0.01">0.01</mat-option>-->
+        <!--        </mat-select>-->
+        <!--      </mat-form-field>-->
+        <!--    </div>-->
+    </sp-configuration-box>
+</sp-visualization-config-outer>
diff --git 
a/ui/src/app/data-explorer/components/widgets/gauge/config/gauge-widget-config.component.ts
 
b/ui/src/app/data-explorer/components/widgets/gauge/config/gauge-widget-config.component.ts
new file mode 100644
index 0000000000..809b0f303b
--- /dev/null
+++ 
b/ui/src/app/data-explorer/components/widgets/gauge/config/gauge-widget-config.component.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 { WidgetConfigurationService } from 
'../../../../services/widget-configuration.service';
+import { DataExplorerFieldProviderService } from 
'../../../../services/data-explorer-field-provider-service';
+import { GaugeVisConfig, GaugeWidgetModel } from '../model/gauge-widget.model';
+import { DataExplorerField } from '@streampipes/platform-services';
+
+@Component({
+    selector: 'sp-data-explorer-gauge-widget-config',
+    templateUrl: './gauge-widget-config.component.html',
+})
+export class GaugeWidgetConfigComponent extends BaseWidgetConfig<
+    GaugeWidgetModel,
+    GaugeVisConfig
+> {
+    constructor(
+        widgetConfigurationService: WidgetConfigurationService,
+        fieldService: DataExplorerFieldProviderService,
+    ) {
+        super(widgetConfigurationService, fieldService);
+    }
+
+    setSelectedProperty(field: DataExplorerField) {
+        this.currentlyConfiguredWidget.visualizationConfig.selectedProperty =
+            field;
+        this.triggerViewRefresh();
+    }
+
+    protected applyWidgetConfig(config: GaugeVisConfig): void {
+        config.selectedProperty = this.fieldService.getSelectedField(
+            config.selectedProperty,
+            this.fieldProvider.numericFields,
+            () => this.fieldProvider.numericFields[0],
+        );
+        config.min ??= 0;
+        config.max ??= 100;
+    }
+
+    protected requiredFieldsForChartPresent(): boolean {
+        return this.fieldProvider.numericFields.length > 0;
+    }
+}
diff --git 
a/ui/src/app/data-explorer/components/widgets/gauge/gauge-renderer.service.ts 
b/ui/src/app/data-explorer/components/widgets/gauge/gauge-renderer.service.ts
new file mode 100644
index 0000000000..e7e6e9d4af
--- /dev/null
+++ 
b/ui/src/app/data-explorer/components/widgets/gauge/gauge-renderer.service.ts
@@ -0,0 +1,118 @@
+/*
+ * 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 { inject, Injectable } from '@angular/core';
+import { GaugeWidgetModel } from './model/gauge-widget.model';
+import { EChartsOption, GaugeSeriesOption } from 'echarts';
+import { FieldUpdateInfo } from '../../../models/field-update.model';
+import {
+    DataExplorerField,
+    SpQueryResult,
+} from '@streampipes/platform-services';
+import {
+    SpEchartsRenderer,
+    WidgetEchartsAppearanceConfig,
+} from '../../../models/dataview-dashboard.model';
+import { WidgetSize } from '../../../models/dataset.model';
+import { EchartsBasicOptionsGeneratorService } from 
'../../../echarts-renderer/echarts-basic-options-generator.service';
+import { SpFieldUpdateService } from '../../../services/field-update.service';
+
+@Injectable({ providedIn: 'root' })
+export class SpGaugeRendererService
+    implements SpEchartsRenderer<GaugeWidgetModel>
+{
+    protected fieldUpdateService = inject(SpFieldUpdateService);
+    protected echartsBaseOptionsGenerator = inject(
+        EchartsBasicOptionsGeneratorService,
+    );
+
+    makeSeriesItem(
+        seriesName: string,
+        fieldName: string,
+        value: number,
+        widgetConfig: GaugeWidgetModel,
+    ): GaugeSeriesOption {
+        const visConfig = widgetConfig.visualizationConfig;
+        return {
+            name: seriesName,
+            type: 'gauge',
+            progress: {
+                show: true,
+            },
+            detail: {
+                show: true,
+                valueAnimation: false,
+                formatter: '{value}',
+            },
+            min: visConfig.min,
+            max: visConfig.max,
+            data: [
+                {
+                    value: value,
+                    name: fieldName,
+                },
+            ],
+        };
+    }
+
+    getSelectedField(widgetConfig: GaugeWidgetModel): DataExplorerField {
+        return widgetConfig.visualizationConfig.selectedProperty;
+    }
+
+    handleUpdatedFields(
+        fieldUpdateInfo: FieldUpdateInfo,
+        widgetConfig: GaugeWidgetModel,
+    ): void {
+        this.fieldUpdateService.updateAnyField(
+            this.getSelectedField(widgetConfig),
+            fieldUpdateInfo,
+        );
+    }
+
+    render(
+        queryResult: SpQueryResult[],
+        widgetConfig: GaugeWidgetModel,
+        _widgetSize: WidgetSize,
+    ): EChartsOption {
+        const option = this.echartsBaseOptionsGenerator.makeBaseConfig(
+            widgetConfig.baseAppearanceConfig as WidgetEchartsAppearanceConfig,
+            {},
+        );
+        const selectedField = this.getSelectedField(widgetConfig);
+        const sourceIndex = selectedField.sourceIndex;
+        const dataSeries = queryResult[sourceIndex].allDataSeries[0];
+        const columnIndex = dataSeries.headers.indexOf(
+            selectedField.fullDbName,
+        );
+        const data = parseFloat(dataSeries.rows[0][columnIndex].toFixed(2));
+        Object.assign(option, {
+            grid: {
+                width: '100%',
+                height: '100%',
+            },
+            series: this.makeSeriesItem(
+                '',
+                selectedField.fullDbName,
+                data,
+                widgetConfig,
+            ),
+        });
+
+        return option;
+    }
+}
diff --git 
a/ui/src/app/data-explorer/components/widgets/gauge/model/gauge-widget.model.ts 
b/ui/src/app/data-explorer/components/widgets/gauge/model/gauge-widget.model.ts
new file mode 100644
index 0000000000..ee76a6ed9c
--- /dev/null
+++ 
b/ui/src/app/data-explorer/components/widgets/gauge/model/gauge-widget.model.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 {
+    DataExplorerDataConfig,
+    DataExplorerField,
+    DataExplorerWidgetModel,
+} from '@streampipes/platform-services';
+import {
+    AxisConfig,
+    DataExplorerVisConfig,
+} from '../../../../models/dataview-dashboard.model';
+
+export interface GaugeVisConfig extends DataExplorerVisConfig {
+    selectedProperty: DataExplorerField;
+    min: number;
+    max: number;
+}
+
+export interface GaugeWidgetModel extends DataExplorerWidgetModel {
+    dataConfig: DataExplorerDataConfig;
+    visualizationConfig: GaugeVisConfig;
+}
diff --git a/ui/src/app/data-explorer/data-explorer.module.ts 
b/ui/src/app/data-explorer/data-explorer.module.ts
index 2fc83096f5..d3f42afa9a 100644
--- a/ui/src/app/data-explorer/data-explorer.module.ts
+++ b/ui/src/app/data-explorer/data-explorer.module.ts
@@ -129,6 +129,7 @@ import { TimeRangeSelectorMenuComponent } from 
'./components/time-selector/time-
 import { CustomTimeRangeSelectionComponent } from 
'./components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component';
 import { DataExplorerRefreshIntervalSettingsComponent } from 
'./components/dashboard/dashboard-toolbar/refresh-interval-settings/refresh-interval-settings.component';
 import { OrderSelectionPanelComponent } from 
'./components/data-view/data-view-designer-panel/data-settings/order-selection-panel/order-selection-panel.component';
+import { GaugeWidgetConfigComponent } from 
'./components/widgets/gauge/config/gauge-widget-config.component';
 
 @NgModule({
     imports: [
@@ -230,6 +231,7 @@ import { OrderSelectionPanelComponent } from 
'./components/data-view/data-view-d
         FieldSelectionPanelComponent,
         FieldSelectionComponent,
         FilterSelectionPanelComponent,
+        GaugeWidgetConfigComponent,
         GroupConfigurationComponent,
         ImageWidgetComponent,
         ImageBarComponent,
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 c37eeb475d..4ef84d7dca 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
@@ -49,12 +49,16 @@ import { TimeSeriesChartWidgetModel } from 
'../components/widgets/time-series-ch
 import { SpTimeseriesRendererService } from 
'../components/widgets/time-series-chart/sp-timeseries-renderer.service';
 import { SpEchartsWidgetAppearanceConfigComponent } from 
'../components/widgets/utils/echarts-widget-appearance-config/echarts-widget-appearance-config.component';
 import { SpTimeSeriesAppearanceConfigComponent } from 
'../components/widgets/time-series-chart/appearance-config/time-series-appearance-config.component';
+import { SpGaugeRendererService } from 
'../components/widgets/gauge/gauge-renderer.service';
+import { GaugeWidgetConfigComponent } from 
'../components/widgets/gauge/config/gauge-widget-config.component';
+import { GaugeWidgetModel } from 
'../components/widgets/gauge/model/gauge-widget.model';
 
 @Injectable({ providedIn: 'root' })
 export class DataExplorerWidgetRegistry {
     widgetTypes: IWidget<any>[] = [];
 
     constructor(
+        private gaugeRenderer: SpGaugeRendererService,
         private heatmapRenderer: SpHeatmapRendererService,
         private histogramRenderer: SpHistogramRendererService,
         private pieRenderer: SpPieRendererService,
@@ -65,6 +69,15 @@ export class DataExplorerWidgetRegistry {
         private timeseriesRenderer: SpTimeseriesRendererService,
     ) {
         this.widgetTypes = [
+            {
+                id: 'gauge',
+                label: 'Gauge',
+                widgetAppearanceConfigurationComponent:
+                    SpEchartsWidgetAppearanceConfigComponent,
+                widgetConfigurationComponent: GaugeWidgetConfigComponent,
+                widgetComponent: SpEchartsWidgetComponent<GaugeWidgetModel>,
+                chartRenderer: this.gaugeRenderer,
+            },
             {
                 id: 'table',
                 label: 'Table',

Reply via email to