This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch improve-chart-types in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 1d4bd3a63e6af6c8cd5233d8a81f341d1b17cec4 Author: Dominik Riemer <[email protected]> AuthorDate: Tue Mar 24 18:28:30 2026 +0100 Let users configure the number of decimals to show --- ...echarts-widget-appearance-config.component.html | 18 ++++++++ .../echarts-widget-appearance-config.component.ts | 37 +++++++++++++++- .../charts/density/density-renderer.service.ts | 18 ++++++++ .../charts/gauge/gauge-renderer.service.ts | 11 ++++- .../charts/heatmap/heatmap-renderer.service.ts | 3 +- .../charts/histogram/histogram-renderer.service.ts | 49 +++++++++++++++++++++- .../components/charts/pie/pie-renderer.service.ts | 23 +++++++--- .../charts/scatter/scatter-renderer.service.ts | 18 ++++++++ .../sp-timeseries-renderer.service.ts | 10 ++++- .../value-heatmap-renderer.service.ts | 5 ++- .../echarts-renderer/base-echarts-renderer.ts | 28 +++++++++++++ .../echarts-basic-options-generator.service.ts | 13 ++++++ .../models/dataview-dashboard.model.ts | 5 +++ 13 files changed, 223 insertions(+), 15 deletions(-) diff --git a/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.html b/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.html index 27aad11995..7b2ee8bed7 100644 --- a/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.html +++ b/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.html @@ -32,4 +32,22 @@ (change)="triggerViewUpdate()" >{{ 'Show tooltip' | translate }} </mat-checkbox> + + <sp-form-field + [level]="3" + [label]="'Decimals' | translate" + [description]="'Number of decimals to show' | translate" + > + <mat-form-field> + <input + matInput + type="number" + [ngModel]="appearanceConfig.numberFormat?.decimals ?? 2" + min="0" + max="10" + step="1" + (ngModelChange)="updateDecimals($event)" + /> + </mat-form-field> + </sp-form-field> </sp-split-section> diff --git a/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.ts b/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.ts index 5c5ab4da66..af7e8b3796 100644 --- a/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.ts +++ b/ui/src/app/chart-shared/components/chart-config/echarts-widget-appearance-config/echarts-widget-appearance-config.component.ts @@ -19,15 +19,28 @@ import { Component, Input, OnInit, inject } from '@angular/core'; import { WidgetEchartsAppearanceConfig } from '../../../models/dataview-dashboard.model'; import { ChartConfigurationService } from '../../../services/chart-configuration.service'; -import { SplitSectionComponent } from '@streampipes/shared-ui'; +import { + FormFieldComponent, + SplitSectionComponent, +} from '@streampipes/shared-ui'; import { MatCheckbox } from '@angular/material/checkbox'; import { FormsModule } from '@angular/forms'; import { TranslatePipe } from '@ngx-translate/core'; +import { MatFormField } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; @Component({ selector: 'sp-echarts-widget-appearance-config', templateUrl: './echarts-widget-appearance-config.component.html', - imports: [SplitSectionComponent, MatCheckbox, FormsModule, TranslatePipe], + imports: [ + SplitSectionComponent, + FormFieldComponent, + MatCheckbox, + MatFormField, + MatInput, + FormsModule, + TranslatePipe, + ], }) export class SpEchartsWidgetAppearanceConfigComponent implements OnInit { private widgetConfigurationService = inject(ChartConfigurationService); @@ -41,6 +54,12 @@ export class SpEchartsWidgetAppearanceConfigComponent implements OnInit { showToolbox: true, showTooltip: true, }; + this.appearanceConfig.numberFormat ??= { + decimals: 2, + }; + this.appearanceConfig.numberFormat.decimals = this.normalizeDecimals( + this.appearanceConfig.numberFormat.decimals, + ); } triggerViewUpdate() { @@ -49,4 +68,18 @@ export class SpEchartsWidgetAppearanceConfigComponent implements OnInit { refreshData: false, }); } + + updateDecimals(decimals: number | string): void { + this.appearanceConfig.numberFormat.decimals = + this.normalizeDecimals(decimals); + this.triggerViewUpdate(); + } + + private normalizeDecimals(decimals: number | string): number { + const parsedDecimals = Number(decimals); + if (!Number.isFinite(parsedDecimals)) { + return 2; + } + return Math.min(10, Math.max(0, Math.round(parsedDecimals))); + } } diff --git a/ui/src/app/chart-shared/components/charts/density/density-renderer.service.ts b/ui/src/app/chart-shared/components/charts/density/density-renderer.service.ts index e61809c1a5..06cc688e13 100644 --- a/ui/src/app/chart-shared/components/charts/density/density-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/density/density-renderer.service.ts @@ -39,12 +39,17 @@ export class SpDensityRendererService extends SpBaseEchartsRenderer<CorrelationC widgetConfig: CorrelationChartWidgetModel, widgetSize: WidgetSize, ): void { + const decimals = this.getDecimals(widgetConfig); const xField = this.getXField(widgetConfig); const yField = this.getYField(widgetConfig); const dataset = this.datasetUtilsService.findPreparedDataset( datasets, xField.sourceIndex, ); + const tooltip = + !Array.isArray(options.tooltip) && options.tooltip + ? options.tooltip + : {}; const data = this.prepareDataset(dataset, xField, yField); const stats = this.calculateStats(data); @@ -65,6 +70,10 @@ export class SpDensityRendererService extends SpBaseEchartsRenderer<CorrelationC scale: true, min: Math.floor(stats.minX - 1), max: Math.ceil(stats.maxX + 1), + axisLabel: { + formatter: (value: number | string) => + this.formatNumber(value, decimals), + }, name: widgetConfig.visualizationConfig.labelX || `${xField.fullDbName}${xField.measurementUnitResourceId ? ` (${xField.measurementUnitResourceId.split('#').pop()})` : ''}`, @@ -78,6 +87,10 @@ export class SpDensityRendererService extends SpBaseEchartsRenderer<CorrelationC scale: true, min: Math.floor(stats.minY - 1), max: Math.ceil(stats.maxY + 1), + axisLabel: { + formatter: (value: number | string) => + this.formatNumber(value, decimals), + }, name: widgetConfig.visualizationConfig.labelY || `${yField.fullDbName}${yField.measurementUnitResourceId ? ` (${yField.measurementUnitResourceId.split('#').pop()})` : ''}`, @@ -100,6 +113,11 @@ export class SpDensityRendererService extends SpBaseEchartsRenderer<CorrelationC color: ['white', 'yellow', 'red'], }, }, + tooltip: { + ...tooltip, + valueFormatter: (value: unknown) => + this.formatNumber(value, decimals), + }, series: [ { name: dataset.rawDataset, diff --git a/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts b/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts index f72bfcf52d..263c62c961 100644 --- a/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts @@ -43,6 +43,7 @@ export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetMode seriesName: string, fieldName: string, value: number, + decimals: number, widgetConfig: GaugeWidgetModel, widgetSize: WidgetSize, gaugeLayout: GaugeLayout, @@ -64,7 +65,8 @@ export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetMode detail: { show: true, valueAnimation: false, - formatter: '{value}', + formatter: (currentValue: number) => + currentValue.toFixed(decimals), fontSize: 14 * clamp, offsetCenter: [0, gaugeLayout.detailOffsetY], }, @@ -102,13 +104,17 @@ export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetMode widgetConfig.baseAppearanceConfig as WidgetEchartsAppearanceConfig, {}, ); + const appearanceConfig = + widgetConfig.baseAppearanceConfig as WidgetEchartsAppearanceConfig; + const decimals = appearanceConfig.numberFormat?.decimals ?? 2; 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)); + const value = Number(dataSeries.rows[0][columnIndex]); + const data = Number.isFinite(value) ? value : 0; const legend = !Array.isArray(option.legend) && option.legend ? option.legend : {}; const toolbox = @@ -139,6 +145,7 @@ export class SpGaugeRendererService implements SpEchartsRenderer<GaugeWidgetMode '', selectedField.fullDbName, data, + decimals, widgetConfig, widgetSize, gaugeLayout, diff --git a/ui/src/app/chart-shared/components/charts/heatmap/heatmap-renderer.service.ts b/ui/src/app/chart-shared/components/charts/heatmap/heatmap-renderer.service.ts index 0fb0af79de..b17ed70b86 100644 --- a/ui/src/app/chart-shared/components/charts/heatmap/heatmap-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/heatmap/heatmap-renderer.service.ts @@ -36,6 +36,7 @@ export class SpHeatmapRendererService extends SpBaseEchartsRenderer<HeatmapWidge widgetConfig: HeatmapWidgetModel, ): void { this.basicOptions(options, widgetConfig); + const decimals = this.getDecimals(widgetConfig); const field = widgetConfig.visualizationConfig.selectedHeatProperty; const sourceIndex = field.sourceIndex; @@ -62,7 +63,7 @@ export class SpHeatmapRendererService extends SpBaseEchartsRenderer<HeatmapWidge return [ index, this.makeTag(rawDataset.rawDataset.dimensions, tags, row), - (row[heatIndex] as number).toFixed(2), + this.formatNumber(row[heatIndex], decimals), ]; }); diff --git a/ui/src/app/chart-shared/components/charts/histogram/histogram-renderer.service.ts b/ui/src/app/chart-shared/components/charts/histogram/histogram-renderer.service.ts index 1ab7f79ca5..33d91bb79a 100644 --- a/ui/src/app/chart-shared/components/charts/histogram/histogram-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/histogram/histogram-renderer.service.ts @@ -29,8 +29,27 @@ export class SpHistogramRendererService extends SpBaseSingleFieldEchartsRenderer HistogramChartWidgetModel, BarSeriesOption > { - addAdditionalConfigs(option: EChartsOption) { - //do nothing + addAdditionalConfigs( + option: EChartsOption, + widgetConfig?: HistogramChartWidgetModel, + ) { + if (!widgetConfig) { + return; + } + + const decimals = this.getDecimals(widgetConfig); + const tooltip = + !Array.isArray(option.tooltip) && option.tooltip + ? option.tooltip + : {}; + option.tooltip = { + ...tooltip, + valueFormatter: (value: unknown) => + this.formatNumber(value, decimals), + }; + + this.applyAxisLabelFormatting(option.xAxis, decimals); + this.applyAxisLabelFormatting(option.yAxis, decimals); } public handleUpdatedFields( @@ -91,4 +110,30 @@ export class SpHistogramRendererService extends SpBaseSingleFieldEchartsRenderer getDefaultSeriesName(widgetConfig: HistogramChartWidgetModel): string { return widgetConfig.visualizationConfig.selectedProperty.fullDbName; } + + private applyAxisLabelFormatting( + axis: EChartsOption['xAxis'] | EChartsOption['yAxis'] | undefined, + decimals: number, + ): void { + if (!axis) { + return; + } + + if (Array.isArray(axis)) { + axis.forEach(a => { + (a as any).axisLabel = { + ...(a as any).axisLabel, + formatter: (value: number | string) => + this.formatNumber(value, decimals), + }; + }); + return; + } + + (axis as any).axisLabel = { + ...(axis as any).axisLabel, + formatter: (value: number | string) => + this.formatNumber(value, decimals), + }; + } } diff --git a/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts b/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts index 8c61215663..5b4aaf3430 100644 --- a/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts @@ -86,11 +86,12 @@ export class SpPieRendererService extends SpBaseSingleFieldEchartsRenderer< addSeriesItem( name: string, datasetIndex: number, - _widgetConfig: PieChartWidgetModel, + widgetConfig: PieChartWidgetModel, ): PieSeriesOption { - const innerRadius = _widgetConfig.visualizationConfig.selectedRadius; + const innerRadius = widgetConfig.visualizationConfig.selectedRadius; const colorMapping = - _widgetConfig.visualizationConfig.colorMappingsPieChart; + widgetConfig.visualizationConfig.colorMappingsPieChart; + const decimals = this.getDecimals(widgetConfig); return { name, @@ -103,7 +104,15 @@ export class SpPieRendererService extends SpBaseSingleFieldEchartsRenderer< colorMapping.find( c => c.value === params.value[0]?.toString(), )?.label || params.value[0]; - return `${params.marker} ${mappedLabel} <b>${params.value[1]}</b> (${params.percent}%)`; + const formattedValue = this.formatNumber( + params.value[1], + decimals, + ); + const formattedPercent = + typeof params.percent === 'number' + ? this.formatNumber(params.percent, decimals) + : params.percent; + return `${params.marker} ${mappedLabel} <b>${formattedValue}</b> (${formattedPercent}%)`; }, }, label: { @@ -112,7 +121,11 @@ export class SpPieRendererService extends SpBaseSingleFieldEchartsRenderer< colorMapping.find( c => c.value === params.value[0]?.toString(), )?.label || params.value[0]; - return `${mappedLabel} (${params.percent}%)`; + const formattedPercent = + typeof params.percent === 'number' + ? this.formatNumber(params.percent, decimals) + : params.percent; + return `${mappedLabel} (${formattedPercent}%)`; }, }, encode: { itemName: 'name', value: 'value' }, diff --git a/ui/src/app/chart-shared/components/charts/scatter/scatter-renderer.service.ts b/ui/src/app/chart-shared/components/charts/scatter/scatter-renderer.service.ts index c25272d75d..cf6802114e 100644 --- a/ui/src/app/chart-shared/components/charts/scatter/scatter-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/scatter/scatter-renderer.service.ts @@ -31,12 +31,17 @@ export class SpScatterRendererService extends SpBaseEchartsRenderer<CorrelationC widgetConfig: CorrelationChartWidgetModel, _widgetSize: WidgetSize, ): void { + const decimals = this.getDecimals(widgetConfig); const xField = this.getXField(widgetConfig); const yField = this.getYField(widgetConfig); const dataset = this.datasetUtilsService.findPreparedDataset( generatedDataset, xField.sourceIndex, ); + const tooltip = + !Array.isArray(options.tooltip) && options.tooltip + ? options.tooltip + : {}; const series = []; for ( let i = 0; @@ -66,6 +71,10 @@ export class SpScatterRendererService extends SpBaseEchartsRenderer<CorrelationC type: 'value', min: 'dataMin', max: 'dataMax', + axisLabel: { + formatter: (value: number | string) => + this.formatNumber(value, decimals), + }, name: widgetConfig.visualizationConfig.labelX || `${xField.fullDbName}${xField.measurementUnitResourceId ? ` (${xField.measurementUnitResourceId.split('#').pop()})` : ''}`, @@ -79,6 +88,10 @@ export class SpScatterRendererService extends SpBaseEchartsRenderer<CorrelationC type: 'value', min: 'dataMin', max: 'dataMax', + axisLabel: { + formatter: (value: number | string) => + this.formatNumber(value, decimals), + }, name: widgetConfig.visualizationConfig.labelY || `${yField.fullDbName}${yField.measurementUnitResourceId ? ` (${yField.measurementUnitResourceId.split('#').pop()})` : ''}`, @@ -89,6 +102,11 @@ export class SpScatterRendererService extends SpBaseEchartsRenderer<CorrelationC nameGap: 40, }, series, + tooltip: { + ...tooltip, + valueFormatter: (value: unknown) => + this.formatNumber(value, decimals), + }, }); } diff --git a/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts b/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts index e0a603bd1c..6cbe67b849 100644 --- a/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts @@ -46,7 +46,8 @@ export class SpTimeseriesRendererService extends SpBaseEchartsRenderer<TimeSerie widgetConfig: TimeSeriesChartWidgetModel, widgetSize: WidgetSize, ): void { - this.addAxisOptions(widgetConfig, options, widgetSize); + const decimals = this.getDecimals(widgetConfig); + this.addAxisOptions(widgetConfig, options, widgetSize, decimals); const finalSeries: SeriesOption[] = []; widgetConfig.visualizationConfig.selectedTimeSeriesChartProperties.forEach( @@ -110,6 +111,8 @@ export class SpTimeseriesRendererService extends SpBaseEchartsRenderer<TimeSerie tooltip: { show: showTooltip, trigger: 'axis', + valueFormatter: (value: unknown) => + this.formatNumber(value, decimals), axisPointer: { type: 'cross', }, @@ -267,6 +270,7 @@ export class SpTimeseriesRendererService extends SpBaseEchartsRenderer<TimeSerie config: TimeSeriesChartWidgetModel, options: EChartsOption, widgetSize: WidgetSize, + decimals: number, ): void { const xAxisOption = this.axisGeneratorService.makeAxis( 'time', @@ -305,6 +309,10 @@ export class SpTimeseriesRendererService extends SpBaseEchartsRenderer<TimeSerie position: axis as CartesianAxisPosition, min: settings.autoScaleActive ? undefined : settings.axisMin, max: settings.autoScaleActive ? undefined : settings.axisMax, + axisLabel: { + formatter: (value: number | string) => + this.formatNumber(value, decimals), + }, }); axisIndex++; }); diff --git a/ui/src/app/chart-shared/components/charts/value-heatmap/value-heatmap-renderer.service.ts b/ui/src/app/chart-shared/components/charts/value-heatmap/value-heatmap-renderer.service.ts index 502ad7c125..18cadbe9b6 100644 --- a/ui/src/app/chart-shared/components/charts/value-heatmap/value-heatmap-renderer.service.ts +++ b/ui/src/app/chart-shared/components/charts/value-heatmap/value-heatmap-renderer.service.ts @@ -92,9 +92,10 @@ export class SpValueHeatmapRendererService extends SpBaseSingleFieldEchartsRende addSeriesItem( name: string, datasetIndex: number, - _widgetConfig: ValueHeatmapChartWidgetModel, + widgetConfig: ValueHeatmapChartWidgetModel, index: number, ): HeatmapSeriesOption { + const decimals = this.getDecimals(widgetConfig); return { universalTransition: true, animation: true, @@ -107,7 +108,7 @@ export class SpValueHeatmapRendererService extends SpBaseSingleFieldEchartsRende tooltip: { valueFormatter: value => { if (typeof value === 'number' && isFinite(value)) { - return (value * 100).toFixed(3) + '%'; + return this.formatNumber(value * 100, decimals) + '%'; } else { return value as string; } diff --git a/ui/src/app/chart-shared/echarts-renderer/base-echarts-renderer.ts b/ui/src/app/chart-shared/echarts-renderer/base-echarts-renderer.ts index 416ad2774b..1b4ecd1991 100644 --- a/ui/src/app/chart-shared/echarts-renderer/base-echarts-renderer.ts +++ b/ui/src/app/chart-shared/echarts-renderer/base-echarts-renderer.ts @@ -102,4 +102,32 @@ export abstract class SpBaseEchartsRenderer< > { return {}; } + + protected getDecimals(widgetConfig: T): number { + const appearanceConfig = + widgetConfig.baseAppearanceConfig as WidgetEchartsAppearanceConfig; + return this.normalizeDecimals(appearanceConfig?.numberFormat?.decimals); + } + + protected formatNumber(value: unknown, decimals: number): string { + const numericValue = typeof value === 'number' ? value : Number(value); + if (Number.isFinite(numericValue)) { + return numericValue.toFixed(this.normalizeDecimals(decimals)); + } + + if (value === null || value === undefined) { + return ''; + } + + return String(value); + } + + private normalizeDecimals(decimals: unknown): number { + const parsedValue = Number(decimals); + if (!Number.isFinite(parsedValue)) { + return 2; + } + + return Math.min(10, Math.max(0, Math.round(parsedValue))); + } } diff --git a/ui/src/app/chart-shared/echarts-renderer/echarts-basic-options-generator.service.ts b/ui/src/app/chart-shared/echarts-renderer/echarts-basic-options-generator.service.ts index 6eab87f218..bae2997adb 100644 --- a/ui/src/app/chart-shared/echarts-renderer/echarts-basic-options-generator.service.ts +++ b/ui/src/app/chart-shared/echarts-renderer/echarts-basic-options-generator.service.ts @@ -35,6 +35,12 @@ export class EchartsBasicOptionsGeneratorService { showLegend: true, showTooltip: true, }; + appearanceConfig.numberFormat ??= { + decimals: 2, + }; + appearanceConfig.numberFormat.decimals = this.normalizeDecimals( + appearanceConfig.numberFormat.decimals, + ); return { legend: { @@ -56,4 +62,11 @@ export class EchartsBasicOptionsGeneratorService { }, }; } + + private normalizeDecimals(decimals: number): number { + if (!Number.isFinite(decimals)) { + return 2; + } + return Math.min(10, Math.max(0, Math.round(decimals))); + } } diff --git a/ui/src/app/chart-shared/models/dataview-dashboard.model.ts b/ui/src/app/chart-shared/models/dataview-dashboard.model.ts index 8a6dc64a9a..a895a9ceae 100644 --- a/ui/src/app/chart-shared/models/dataview-dashboard.model.ts +++ b/ui/src/app/chart-shared/models/dataview-dashboard.model.ts @@ -100,6 +100,10 @@ export interface WidgetChartAppearanceConfig { showTooltip: boolean; } +export interface WidgetNumberFormatConfig { + decimals: number; +} + export interface DataZoomConfig { show: boolean; type: 'slider' | 'inside'; @@ -111,6 +115,7 @@ export interface TimeSeriesAppearanceConfig extends WidgetEchartsAppearanceConfi export interface WidgetEchartsAppearanceConfig { chartAppearance: WidgetChartAppearanceConfig; + numberFormat?: WidgetNumberFormatConfig; } export interface WidgetBaseAppearanceConfig {
