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',
