From 63cc15091c7257cc8bdcf2dc9c16b4499f756709 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 2 Dec 2025 13:49:32 +0200 Subject: [PATCH 1/8] Added support for dynamic y axis limits determination --- .../chart/bar-chart-basic-config.component.ts | 30 +++ ...rt-with-labels-basic-config.component.html | 3 + .../latest-chart-basic-config.component.html | 78 ++++-- ...polar-area-chart-basic-config.component.ts | 30 +++ .../range-chart-basic-config.component.html | 3 + ...e-series-chart-basic-config.component.html | 3 + .../lib/chart/bar-chart-widget.models.ts | 5 +- .../widget/lib/chart/bars-chart.models.ts | 5 +- .../components/widget/lib/chart/bars-chart.ts | 225 +++++++++++++++++- .../lib/chart/time-series-chart.models.ts | 42 +++- .../widget/lib/chart/time-series-chart.ts | 201 ++++++++++++++++ .../bar-chart-widget-settings.component.ts | 30 +++ ...with-labels-widget-settings.component.html | 3 + ...atest-chart-widget-settings.component.html | 80 +++++-- .../latest-chart-widget-settings.component.ts | 10 + ...ar-area-chart-widget-settings.component.ts | 33 ++- ...range-chart-widget-settings.component.html | 3 + .../common/axis-scale-row.component.html | 71 ++++++ .../common/axis-scale-row.component.scss | 15 ++ .../common/axis-scale-row.component.ts | 204 ++++++++++++++++ ...s-chart-axis-settings-panel.component.html | 3 + ...ies-chart-axis-settings-panel.component.ts | 12 + ...-series-chart-axis-settings.component.html | 48 ++-- ...me-series-chart-axis-settings.component.ts | 41 +++- ...ime-series-chart-y-axes-panel.component.ts | 12 + .../time-series-chart-y-axis-row.component.ts | 7 +- .../common/widget-settings-common.module.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 8 + ui-ngx/tailwind.config.js | 3 +- 29 files changed, 1136 insertions(+), 79 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts index ba9c41893e..9f036098be 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts @@ -29,6 +29,8 @@ import { import { LatestChartBasicConfigComponent } from '@home/components/widget/config/basic/chart/latest-chart-basic-config.component'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-bar-chart-basic-config', @@ -77,4 +79,32 @@ export class BarChartBasicConfigComponent extends LatestChartBasicConfigComponen this.widgetConfig.config.settings.axisTickLabelFont = config.axisTickLabelFont; this.widgetConfig.config.settings.axisTickLabelColor = config.axisTickLabelColor; } + + protected onConfigSet(configData: WidgetConfigComponentData) { + configData.config.settings.axisMin = this.normalizeAxisLimit(configData.config.settings.axisMin); + configData.config.settings.axisMax = this.normalizeAxisLimit(configData.config.settings.axisMax); + super.onConfigSet(configData); + } + + private normalizeAxisLimit(limit: any): AxisLimitConfig { + if (limit && typeof limit === 'object' && 'type' in limit) { + return { + type: limit.type || ValueSourceType.constant, + value: limit.value ?? null, + entityAlias: limit.entityAlias ?? null + }; + } + if (typeof limit === 'number') { + return { + type: ValueSourceType.constant, + value: limit, + entityAlias: null + }; + } + return { + type: ValueSourceType.constant, + value: null, + entityAlias: null + }; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html index 307645822d..f100ba8c88 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html @@ -173,6 +173,9 @@
widgets.time-series-chart.axis.y-axis
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html index 31d0e6cd7e..a1b2770085 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html @@ -346,18 +346,8 @@
widgets.bar-chart.bar-axis
-
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-appearance
-
widgets.chart.chart-axis.scale-min
- - - -
widgets.chart.chart-axis.scale-max
- - -
+
+
widgets.chart.chart-axis.scale-limits
+
+
+
widgets.chart.chart-axis.limit
+
widgets.chart.chart-axis.source
+
widgets.chart.chart-axis.key-value
+
+
+ + + + + +
+
+
@@ -386,18 +403,8 @@
widgets.polar-area-chart.polar-axis
-
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-appearance
-
widgets.chart.chart-axis.scale-min
- - - -
widgets.chart.chart-axis.scale-max
- - -
+
+
widgets.chart.chart-axis.scale-limits
+
+
+
widgets.chart.chart-axis.limit
+
widgets.chart.chart-axis.source
+
widgets.chart.chart-axis.key-value
+
+
+ + + + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts index e0dc692e3d..c38f9e9143 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts @@ -29,6 +29,8 @@ import { import { LatestChartBasicConfigComponent } from '@home/components/widget/config/basic/chart/latest-chart-basic-config.component'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-polar-area-chart-basic-config', @@ -79,4 +81,32 @@ export class PolarAreaChartBasicConfigComponent extends LatestChartBasicConfigCo this.widgetConfig.config.settings.axisTickLabelColor = config.axisTickLabelColor; this.widgetConfig.config.settings.angleAxisStartAngle = config.angleAxisStartAngle; } + + protected onConfigSet(configData: WidgetConfigComponentData) { + configData.config.settings.axisMin = this.normalizeAxisLimit(configData.config.settings.axisMin); + configData.config.settings.axisMax = this.normalizeAxisLimit(configData.config.settings.axisMax); + super.onConfigSet(configData); + } + + private normalizeAxisLimit(limit: any): AxisLimitConfig { + if (limit && typeof limit === 'object' && 'type' in limit) { + return { + type: limit.type || ValueSourceType.constant, + value: limit.value ?? null, + entityAlias: limit.entityAlias ?? null + }; + } + if (typeof limit === 'number') { + return { + type: ValueSourceType.constant, + value: limit, + entityAlias: null + }; + } + return { + type: ValueSourceType.constant, + value: null, + entityAlias: null + }; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html index 608284996e..174df4c692 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html @@ -235,6 +235,9 @@
widgets.time-series-chart.axis.y-axis
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index 0c5eb426b3..dd535dfd4d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -103,6 +103,9 @@ formControlName="states"> diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts index 3f76eb18cf..0fd3b027ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts @@ -31,10 +31,11 @@ import { ChartBarSettings, chartColorScheme } from '@home/components/widget/lib/chart/chart.models'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; export interface BarChartWidgetSettings extends LatestChartWidgetSettings { - axisMin?: number; - axisMax?: number; + axisMin?: number | string | AxisLimitConfig; + axisMax?: number | string | AxisLimitConfig; axisTickLabelFont: Font; axisTickLabelColor: string; barSettings: ChartBarSettings; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts index 2808d9f7e5..860f4da5b9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts @@ -24,11 +24,12 @@ import { chartColorScheme } from '@home/components/widget/lib/chart/chart.models'; import { Font } from '@shared/models/widget-settings.models'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; export interface BarsChartSettings extends LatestChartSettings { polar: boolean; - axisMin?: number | string; - axisMax?: number | string; + axisMin?: number | string | AxisLimitConfig; + axisMax?: number | string | AxisLimitConfig; axisTickLabelFont: Font; axisTickLabelColor: string; angleAxisStartAngle?: number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts index 92df6dd363..61adae9c20 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts @@ -25,7 +25,7 @@ import { ComponentStyle } from '@shared/models/widget-settings.models'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; import { BarDataItemOption, BarSeriesLabelOption } from 'echarts/types/src/chart/bar/BarSeries'; -import { isDefinedAndNotNull } from '@core/utils'; +import { isDefinedAndNotNull, isNumber, isString } from '@core/utils'; import { ChartFillType, ChartLabelPosition, @@ -35,9 +35,19 @@ import { } from '@home/components/widget/lib/chart/chart.models'; import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes'; import { RadiusAxisOption, YAXisOption } from 'echarts/types/dist/shared'; +import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; +import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { ValueSourceType } from '@shared/models/widget-settings.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; export class TbBarsChart extends TbLatestChart { + private dynamicAxisMin: number | string = null; + private dynamicAxisMax: number | string = null; + private minLatestDataKey: DataKey = null; + private maxLatestDataKey: DataKey = null; + constructor(ctx: WidgetContext, inputSettings: DeepPartial, chartElement: HTMLElement, @@ -52,6 +62,212 @@ export class TbBarsChart extends TbLatestChart { return barsChartDefaultSettings; } + protected initSettings() { + super.initSettings(); + this.setupAxisLimits(); + } + + private setupAxisLimits(): void { + const axisLimitDatasources: Datasource[] = []; + + if (isDefinedAndNotNull(this.settings.axisMin)) { + this.processAxisLimit(this.settings.axisMin, 'min', axisLimitDatasources); + } + if (isDefinedAndNotNull(this.settings.axisMax)) { + this.processAxisLimit(this.settings.axisMax, 'max', axisLimitDatasources); + } + + this.subscribeForAxisLimits(axisLimitDatasources); + } + + private processAxisLimit( + limit: any, + limitType: 'min' | 'max', + axisLimitDatasources: Datasource[] + ): void { + if (limit && typeof limit === 'object' && 'type' in limit) { + const axisLimit = limit as AxisLimitConfig; + + if (axisLimit.type === ValueSourceType.latestKey) { + let latestDataKey: DataKey = null; + if (this.ctx.datasources.length) { + for (const datasource of this.ctx.datasources) { + latestDataKey = datasource.latestDataKeys?.find(d => + (d.type === DataKeyType.function && d.label === (axisLimit.value as DataKey).label) || + (d.type !== DataKeyType.function && d.name === (axisLimit.value as DataKey).name && + d.type === (axisLimit.value as DataKey).type)); + if (latestDataKey) { + break; + } + } + } + + if (latestDataKey) { + if (limitType === 'min') { + this.minLatestDataKey = latestDataKey; + } else { + this.maxLatestDataKey = latestDataKey; + } + } + } else if (axisLimit.type === ValueSourceType.entity) { + const entityAliasId = this.ctx.aliasController.getEntityAliasId(axisLimit.entityAlias); + if (entityAliasId) { + let datasource = axisLimitDatasources.find(d => d.entityAliasId === entityAliasId); + const entityDataKey: DataKey = { + type: (axisLimit.value as DataKey).type, + name: (axisLimit.value as DataKey).name, + label: (axisLimit.value as DataKey).name, + settings: { + axisLimit: limitType + } + }; + + if (datasource) { + datasource.dataKeys.push(entityDataKey); + } else { + datasource = { + type: DatasourceType.entity, + name: axisLimit.entityAlias, + aliasName: axisLimit.entityAlias, + entityAliasId, + dataKeys: [entityDataKey] + }; + axisLimitDatasources.push(datasource); + } + } + } else if (axisLimit.type === ValueSourceType.constant) { + const value = axisLimit.value as number; + if (limitType === 'min') { + this.dynamicAxisMin = value; + } else { + this.dynamicAxisMax = value; + } + } + } else if (typeof limit === 'number' || typeof limit === 'string') { + if (limitType === 'min') { + this.dynamicAxisMin = limit; + } else { + this.dynamicAxisMax = limit; + } + } + } + + private subscribeForAxisLimits(datasources: Datasource[]) { + if (datasources.length) { + const axisLimitsSubscriptionOptions: WidgetSubscriptionOptions = { + datasources, + useDashboardTimewindow: false, + type: widgetType.latest, + callbacks: { + onDataUpdated: (subscription) => { + let update = false; + if (subscription.data) { + for (const data of subscription.data) { + const limitType = data.dataKey.settings.axisLimit as ('min' | 'max'); + if (data.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1]); + if (limitType === 'min') { + if (this.dynamicAxisMin !== value) { + this.dynamicAxisMin = value; + update = true; + } + } else { + if (this.dynamicAxisMax !== value) { + this.dynamicAxisMax = value; + update = true; + } + } + } + } + } + if (this.latestChart && update) { + this.updateAxisLimits(); + } + } + } + }; + this.ctx.subscriptionApi.createSubscription(axisLimitsSubscriptionOptions, true).subscribe(); + } + } + + private parseAxisLimitData(data: any): number | null { + let value: number; + if (isDefinedAndNotNull(data)) { + if (isNumber(data)) { + value = data; + } else if (isString(data)) { + value = Number(data); + } + } + if (isDefinedAndNotNull(value) && !isNaN(value)) { + return value; + } + return null; + } + + private updateAxisLimitsFromLatest(): boolean { + let update = false; + + if (this.ctx.latestData) { + if (this.minLatestDataKey) { + const data = this.ctx.latestData.find(d => d.dataKey === this.minLatestDataKey); + if (data?.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1]); + if (this.dynamicAxisMin !== value) { + this.dynamicAxisMin = value; + update = true; + } + } + } + + if (this.maxLatestDataKey) { + const data = this.ctx.latestData.find(d => d.dataKey === this.maxLatestDataKey); + if (data?.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1]); + if (this.dynamicAxisMax !== value) { + this.dynamicAxisMax = value; + update = true; + } + } + } + } + return update; + } + + private updateAxisLimits(): void { + if (this.latestChart && !this.latestChart.isDisposed()) { + const axisTickLabelStyle = createChartTextStyle(this.settings.axisTickLabelFont, + this.settings.axisTickLabelColor, false, 'axis.tickLabel'); + const valueAxis: ValueAxisBaseOption = { + type: 'value', + min: this.dynamicAxisMin, + max: this.dynamicAxisMax, + axisLabel: { + color: axisTickLabelStyle.color, + fontStyle: axisTickLabelStyle.fontStyle, + fontWeight: axisTickLabelStyle.fontWeight, + fontFamily: axisTickLabelStyle.fontFamily, + fontSize: axisTickLabelStyle.fontSize, + formatter: (value: any) => this.valueFormatter.format(value) + } + }; + + if (this.settings.polar) { + this.latestChartOption.radiusAxis = valueAxis as RadiusAxisOption; + } else { + this.latestChartOption.yAxis = valueAxis as YAXisOption; + } + + this.latestChart.setOption(this.latestChartOption); + } + } + + public latestUpdated() { + if (this.updateAxisLimitsFromLatest()) { + this.updateAxisLimits(); + } + } + protected prepareLatestChartOption() { let labelStyle: ComponentStyle = {}; if (this.settings.barSettings.showLabel) { @@ -89,10 +305,12 @@ export class TbBarsChart extends TbLatestChart { const axisTickLabelStyle = createChartTextStyle(this.settings.axisTickLabelFont, this.settings.axisTickLabelColor, false, 'axis.tickLabel'); + const minValue = isDefinedAndNotNull(this.dynamicAxisMin) ? this.dynamicAxisMin : undefined; + const maxValue = isDefinedAndNotNull(this.dynamicAxisMax) ? this.dynamicAxisMax : undefined; const valueAxis: ValueAxisBaseOption = { type: 'value', - min: this.settings.axisMin, - max: this.settings.axisMax, + min: minValue, + max: maxValue, axisLabel: { color: axisTickLabelStyle.color, fontStyle: axisTickLabelStyle.fontStyle, @@ -102,6 +320,7 @@ export class TbBarsChart extends TbLatestChart { formatter: (value: any) => this.valueFormatter.format(value) } }; + if (this.settings.polar) { this.latestChartOption.polar = { radius: '100%' diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 089c655224..b3924154d6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -381,12 +381,18 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting decimals?: number; interval?: number; splitNumber?: number; - min?: number | string; - max?: number | string; + min?: number | string | AxisLimitConfig; + max?: number | string | AxisLimitConfig; ticksGenerator?: TimeSeriesChartTicksGenerator | string; ticksFormatter?: TimeSeriesChartTicksFormatter | string; } +export interface AxisLimitConfig { + entityAlias: string; + type: ValueSourceType; + value: number | DataKey; +} + export const timeSeriesChartYAxisValid = (axis: TimeSeriesChartYAxisSettings): boolean => !(!axis.id || isUndefinedOrNull(axis.order)); @@ -867,6 +873,11 @@ export interface TimeSeriesChartAxis { id: string; settings: TimeSeriesChartAxisSettings; option: CartesianAxisOption; + dynamicMin?: string| number; + dynamicMax?: string| number; + minLatestDataKey?: DataKey; + maxLatestDataKey?: DataKey; + unitConvertor?: (value: number) => number; } export interface TimeSeriesChartYAxis extends TimeSeriesChartAxis { @@ -926,6 +937,29 @@ export const createTimeSeriesYAxis = (units: string, return ticks?.filter(tick => tick.value >= extent[0] && tick.value <= extent[1]); }; } + + let initialMin: number | string | undefined; + if (isDefinedAndNotNull(settings.min)) { + if (typeof settings.min === 'object' && 'type' in settings.min) { + initialMin = undefined; + } else if (typeof settings.min === 'number') { + initialMin = unitConvertor ? unitConvertor(settings.min) : settings.min; + } else if (typeof settings.min === 'string') { + initialMin = settings.min; + } + } + + let initialMax: number | string | undefined; + if (isDefinedAndNotNull(settings.max)) { + if (typeof settings.max === 'object' && 'type' in settings.max) { + initialMax = undefined; + } else if (typeof settings.max === 'number') { + initialMax = unitConvertor ? unitConvertor(settings.max) : settings.max; + } else if (typeof settings.max === 'string') { + initialMax = settings.max; + } + } + return { id: settings.id, decimals, @@ -939,8 +973,8 @@ export const createTimeSeriesYAxis = (units: string, offset: 0, alignTicks: true, scale: true, - min: isDefinedAndNotNull(settings.min) ? unitConvertor(Number(settings.min)) : settings.min, - max: isDefinedAndNotNull(settings.max) ? unitConvertor(Number(settings.max)) : settings.max, + min: initialMin, + max: initialMax, minInterval, splitNumber, interval, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index d447b51b37..7954352bb4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -17,6 +17,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; import { adjustTimeAxisExtentToData, + AxisLimitConfig, calculateThresholdsOffset, createTimeSeriesVisualMapOption, createTimeSeriesXAxis, @@ -61,6 +62,8 @@ import { isDefined, isDefinedAndNotNull, isEqual, + isNumber, + isString, mergeDeep } from '@core/utils'; import { DataKey, Datasource, DatasourceType, FormattedData, widgetType } from '@shared/models/widget.models'; @@ -287,8 +290,14 @@ export class TbTimeSeriesChart { } } } + if (this.updateAxisLimitsFromLatest()) { + update = true; + } if (this.timeSeriesChart && update) { this.updateSeriesData(); + if (this.updateAxisLimitsFromLatest()) { + this.updateAxisLimits(); + } } } @@ -550,6 +559,7 @@ export class TbTimeSeriesChart { private setupYAxes(): void { const yAxisSettingsList = Object.values(this.settings.yAxes); yAxisSettingsList.sort((a1, a2) => a1.order - a2.order); + const axisLimitDatasources: Datasource[] = []; for (const yAxisSettings of yAxisSettingsList) { const axisSettings = mergeDeep({} as TimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings, yAxisSettings); @@ -563,8 +573,98 @@ export class TbTimeSeriesChart { axisSettings.ticksFormatter = this.stateValueConverter.ticksFormatter; } const yAxis = createTimeSeriesYAxis(unitSymbol, decimals, axisSettings, this.ctx.utilsService, this.darkMode, unitConvertor); + if (isDefinedAndNotNull(axisSettings.min)) { + this.processAxisLimit(axisSettings.min, 'min', yAxis, axisLimitDatasources, unitConvertor); + } + if (isDefinedAndNotNull(axisSettings.max)) { + this.processAxisLimit(axisSettings.max, 'max', yAxis, axisLimitDatasources, unitConvertor); + } + if (yAxis.minLatestDataKey || yAxis.maxLatestDataKey) { + yAxis.unitConvertor = unitConvertor; + } this.yAxisList.push(yAxis); } + this.subscribeForAxisLimits(axisLimitDatasources); + } + + private processAxisLimit( + limit: any, + limitType: 'min' | 'max', + yAxis: TimeSeriesChartYAxis, + axisLimitDatasources: Datasource[], + unitConvertor?: (value: number) => number + ): void { + if (limit && typeof limit === 'object' && 'type' in limit) { + const axisLimit = limit as AxisLimitConfig; + if (axisLimit.type === ValueSourceType.latestKey) { + let latestDataKey: DataKey = null; + if (this.ctx.datasources.length) { + for (const datasource of this.ctx.datasources) { + latestDataKey = datasource.latestDataKeys?.find(d => + (d.type === DataKeyType.function && d.label === (axisLimit.value as DataKey).label) || + (d.type !== DataKeyType.function && d.name === (axisLimit.value as DataKey).name && + d.type === (axisLimit.value as DataKey).type)); + if (latestDataKey) { + break; + } + } + } + if (latestDataKey) { + if (limitType === 'min') { + yAxis.minLatestDataKey = latestDataKey; + } else { + yAxis.maxLatestDataKey = latestDataKey; + } + } + } else if (axisLimit.type === ValueSourceType.entity) { + const entityAliasId = this.ctx.aliasController.getEntityAliasId(axisLimit.entityAlias); + if (entityAliasId) { + let datasource = axisLimitDatasources.find(d => d.entityAliasId === entityAliasId); + const entityDataKey: DataKey = { + type: (axisLimit.value as DataKey).type, + name: (axisLimit.value as DataKey).name, + label: (axisLimit.value as DataKey).name, + settings: { + yAxisId: yAxis.id, + axisLimit: limitType + } + }; + if (datasource) { + datasource.dataKeys.push(entityDataKey); + } else { + datasource = { + type: DatasourceType.entity, + name: axisLimit.entityAlias, + aliasName: axisLimit.entityAlias, + entityAliasId, + dataKeys: [entityDataKey] + }; + axisLimitDatasources.push(datasource); + } + } + } else if (axisLimit.type === ValueSourceType.constant) { + const value = unitConvertor ? unitConvertor(axisLimit.value as number) : axisLimit.value as number; + if (limitType === 'min') { + yAxis.dynamicMin = value; + yAxis.option.min = value; + } else { + yAxis.dynamicMax = value; + yAxis.option.max = value; + } + } + } else if (typeof limit === 'number' || typeof limit === 'string') { + let value = limit; + if (typeof value === 'number' && unitConvertor) { + value = unitConvertor(value); + } + if (limitType === 'min') { + yAxis.dynamicMin = value; + yAxis.option.min = value; + } else { + yAxis.dynamicMax = value; + yAxis.option.max = value; + } + } } private setupVisualMap(): void { @@ -622,6 +722,45 @@ export class TbTimeSeriesChart { } } + private subscribeForAxisLimits(datasources: Datasource[]) { + if (datasources.length) { + const axisLimitsSubscriptionOptions: WidgetSubscriptionOptions = { + datasources, + useDashboardTimewindow: false, + type: widgetType.latest, + callbacks: { + onDataUpdated: (subscription) => { + let update = false; + if (subscription.data) { + for (const yAxis of this.yAxisList) { + for (const data of subscription.data) { + if (data.dataKey.settings?.yAxisId === yAxis.id) { + const limitType = data.dataKey.settings.axisLimit as ('min' | 'max'); + if (data.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); + if (limitType === 'min') { + yAxis.dynamicMin = value; + yAxis.option.min = value; + } else { + yAxis.dynamicMax = value; + yAxis.option.max = value; + } + update = true; + } + } + } + } + } + if (this.timeSeriesChart && update) { + this.updateAxisLimits(); + } + } + } + }; + this.ctx.subscriptionApi.createSubscription(axisLimitsSubscriptionOptions, true).subscribe(); + } + } + private drawChart() { echartsModule.init(); this.renderer.setStyle(this.chartElement, 'letterSpacing', 'normal'); @@ -714,6 +853,58 @@ export class TbTimeSeriesChart { } } + private updateAxisLimitsFromLatest(): boolean { + let update = false; + + if (this.ctx.latestData) { + for (const yAxis of this.yAxisList) { + if (yAxis.minLatestDataKey) { + const data = this.ctx.latestData.find(d => d.dataKey === yAxis.minLatestDataKey); + if (data?.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); + + if (yAxis.dynamicMin !== value) { + yAxis.dynamicMin = value; + yAxis.option.min = value; + update = true; + } + } + } + + if (yAxis.maxLatestDataKey) { + const data = this.ctx.latestData.find(d => d.dataKey === yAxis.maxLatestDataKey); + if (data?.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); + if (yAxis.dynamicMax !== value) { + yAxis.dynamicMax = value; + yAxis.option.max = value; + update = true; + } + } + } + } + } + return update; + } + + private parseAxisLimitData = (data: any, unitConvertor?: (value: number) => number): number => { + let value: number; + if (isDefinedAndNotNull(data)) { + if (isNumber(data)) { + value = data; + } else if (isString(data)) { + value = Number(data); + } + } + if (isDefinedAndNotNull(value) && !isNaN(value)) { + if (unitConvertor) { + return unitConvertor(value); + } + return value; + } + return null; + }; + private updateSeries(): void { this.timeSeriesChartOptions.series = generateChartData(this.dataItems, this.thresholdItems, this.stackMode, @@ -836,6 +1027,16 @@ export class TbTimeSeriesChart { return changed; } + private updateAxisLimits(): void { + if (this.timeSeriesChart && !this.timeSeriesChart.isDisposed()) { + this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + this.timeSeriesChart.setOption(this.timeSeriesChartOptions, { + replaceMerge: ['yAxis'] + }); + this.updateAxes(); + } + } + private scaleYAxis(yAxis: TimeSeriesChartYAxis): boolean { if (!this.stateData) { const axisBarDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled && diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts index 8826e9afda..b1734ff45a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts @@ -26,6 +26,8 @@ import { import { LatestChartWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/latest-chart-widget-settings.component'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-bar-chart-widget-settings', @@ -57,4 +59,32 @@ export class BarChartWidgetSettingsComponent extends LatestChartWidgetSettingsCo latestChartWidgetSettingsForm.addControl('axisTickLabelFont', this.fb.control(settings.axisTickLabelFont, [])); latestChartWidgetSettingsForm.addControl('axisTickLabelColor', this.fb.control(settings.axisTickLabelColor, [])); } + + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + settings.axisMin = this.normalizeAxisLimit(settings.axisMin); + settings.axisMax = this.normalizeAxisLimit(settings.axisMax); + return super.prepareInputSettings(settings); + } + + private normalizeAxisLimit(limit: any): AxisLimitConfig { + if (limit && typeof limit === 'object' && 'type' in limit) { + return { + type: limit.type || ValueSourceType.constant, + value: limit.value ?? null, + entityAlias: limit.entityAlias ?? null + }; + } + if (typeof limit === 'number') { + return { + type: ValueSourceType.constant, + value: limit, + entityAlias: null + }; + } + return { + type: ValueSourceType.constant, + value: null, + entityAlias: null + }; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html index 488a08e161..08dd087cdb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html @@ -91,6 +91,9 @@
widgets.time-series-chart.axis.y-axis
widgets.bar-chart.bar-axis
-
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-appearance
-
widgets.chart.chart-axis.scale-min
- - - -
widgets.chart.chart-axis.scale-max
- - -
+
+
widgets.chart.chart-axis.scale-limits
+
+
+
widgets.chart.chart-axis.limit
+
widgets.chart.chart-axis.source
+
widgets.chart.chart-axis.key-value
+
+
+
+ + + + + +
+
+
+
@@ -311,18 +330,8 @@
widgets.polar-area-chart.polar-axis
-
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-appearance
-
widgets.chart.chart-axis.scale-min
- - - -
widgets.chart.chart-axis.scale-max
- - -
+
+
widgets.chart.chart-axis.scale-limits
+
+
+
widgets.chart.chart-axis.limit
+
widgets.chart.chart-axis.source
+
widgets.chart.chart-axis.key-value
+
+
+ + + + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts index ff7641bf1a..fadee36370 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts @@ -22,6 +22,7 @@ import { LatestChartWidgetSettings } from '@home/components/widget/lib/chart/latest-chart.models'; import { + Datasource, legendPositions, legendPositionTranslationMap, WidgetSettings, @@ -103,6 +104,15 @@ export abstract class LatestChartWidgetSettingsComponent, protected fb: UntypedFormBuilder) { super(store); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts index 07d43f4de2..61c533a377 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts @@ -26,6 +26,8 @@ import { import { LatestChartWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/latest-chart-widget-settings.component'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-polar-area-chart-widget-settings', @@ -52,10 +54,37 @@ export class PolarAreaChartWidgetSettingsComponent extends LatestChartWidgetSett protected setupLatestChartControls(latestChartWidgetSettingsForm: UntypedFormGroup, settings: WidgetSettings) { latestChartWidgetSettingsForm.addControl('barSettings', this.fb.control(settings.barSettings, [])); - latestChartWidgetSettingsForm.addControl('axisMin', this.fb.control(settings.axisMin, [])); - latestChartWidgetSettingsForm.addControl('axisMax', this.fb.control(settings.axisMax, [])); + latestChartWidgetSettingsForm.addControl('axisMin', this.fb.control(settings.axisMin)); + latestChartWidgetSettingsForm.addControl('axisMax', this.fb.control(settings.axisMax)); latestChartWidgetSettingsForm.addControl('axisTickLabelFont', this.fb.control(settings.axisTickLabelFont, [])); latestChartWidgetSettingsForm.addControl('axisTickLabelColor', this.fb.control(settings.axisTickLabelColor, [Validators.min(0)])); latestChartWidgetSettingsForm.addControl('angleAxisStartAngle', this.fb.control(settings.angleAxisStartAngle, [])); } + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + settings.axisMin = this.normalizeAxisLimit(settings.axisMin); + settings.axisMax = this.normalizeAxisLimit(settings.axisMax); + return super.prepareInputSettings(settings); + } + + private normalizeAxisLimit(limit: any): AxisLimitConfig { + if (limit && typeof limit === 'object' && 'type' in limit) { + return { + type: limit.type || ValueSourceType.constant, + value: limit.value ?? null, + entityAlias: limit.entityAlias ?? null + }; + } + if (typeof limit === 'number') { + return { + type: ValueSourceType.constant, + value: limit, + entityAlias: null + }; + } + return { + type: ValueSourceType.constant, + value: null, + entityAlias: null + }; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html index f19784735c..c27b7c46b6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html @@ -169,6 +169,9 @@
widgets.time-series-chart.axis.y-axis
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html new file mode 100644 index 0000000000..eed985187d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html @@ -0,0 +1,71 @@ + +
+
+ {{ labelKey | translate}} +
+
+ + + + {{ ValueSourceTypeTranslation.get(type) | translate }} + + + + + +
+
+ + + + + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss new file mode 100644 index 0000000000..1a4214be18 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss @@ -0,0 +1,15 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed 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. + */ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts new file mode 100644 index 0000000000..b8103dd008 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts @@ -0,0 +1,204 @@ +/// +/// Copyright © 2016-2025 The Thingsboard Authors +/// +/// Licensed 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, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, ValidationErrors, Validator, + Validators, +} from '@angular/forms'; +import { ValueSourceType, ValueSourceTypes, ValueSourceTypeTranslation } from '@shared/models/widget-settings.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { Datasource, DatasourceType, } from '@shared/models/widget.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { IAliasController } from '@core/api/widget-api.models'; +import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; +import { isEqual } from '@core/utils'; + +@Component({ + selector: 'tb-axis-scale-row', + templateUrl: './axis-scale-row.component.html', + styleUrl: './axis-scale-row.component.scss', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AxisScaleRowComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => AxisScaleRowComponent), + multi: true + }, + ] +}) +export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() + isPanelView = false; + + @Input() + aliasController: IAliasController; + + @Input() + callbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + + @Input() + labelKey: string; + + ValueSourceType = ValueSourceType; + + DataKeyType = DataKeyType; + + DatasourceType = DatasourceType; + + ValueSourceTypeTranslation = ValueSourceTypeTranslation; + + ValueSourceTypes = ValueSourceTypes; + + limitForm: UntypedFormGroup; + + private propagateChanges: (value: any) => void = () => {}; + + private modelValue: AxisLimitConfig | null = null; + + constructor(private fb: UntypedFormBuilder, + private destroyRef: DestroyRef) { + } + + ngOnInit() { + this.limitForm = this.fb.group({ + type: [ValueSourceType.constant], + value: [null], + entityAlias: [null] + }); + this.subscribeToTypeChanges(); + + this.limitForm.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(value => { + this.updateValidators(); + this.updateView(value); + }); + } + + writeValue(value: AxisLimitConfig) { + this.modelValue = value; + + if (!this.limitForm) { + return; + } + + if (value) { + this.limitForm.patchValue({ + type: value.type ?? ValueSourceType.constant, + value: value.value ?? null, + entityAlias: value.entityAlias ?? null + }, + { emitEvent: false } + ); + } else { + this.limitForm.patchValue({ + type: ValueSourceType.constant, + value: null, + entityAlias: null + }, + { emitEvent: false } + ); + } + + this.updateValidators(); + } + + registerOnChange(fn: any) { + this.propagateChanges = fn; + } + + registerOnTouched(fn: any) { + } + + validate(): ValidationErrors | null { + return this.limitForm.valid ? null : { + axisLimitForm: { + valid: false, + errors: this.getFormErrors() + } + }; + } + + private getFormErrors(): any { + const errors: any = {}; + Object.keys(this.limitForm.controls).forEach(key => { + const control = this.limitForm.get(key); + if (control && control.errors) { + errors[key] = control.errors; + } + }); + return errors; + } + + private updateValidators() { + const axisTypeControl = this.limitForm.get('type'); + const axisValueControl = this.limitForm.get('value'); + const axisEntityAliasControl = this.limitForm.get('entityAlias'); + + if (axisTypeControl && axisValueControl && axisEntityAliasControl) { + const type = axisTypeControl.value; + if (type === ValueSourceType.latestKey || type === ValueSourceType.entity) { + axisValueControl.setValidators([Validators.required]); + } else { + axisValueControl.clearValidators(); + } + + if (type === ValueSourceType.entity) { + axisEntityAliasControl.setValidators([Validators.required]); + } else { + axisEntityAliasControl.clearValidators(); + } + + axisValueControl.updateValueAndValidity({ emitEvent: false }); + axisEntityAliasControl.updateValueAndValidity({ emitEvent: false }); + } + } + + private subscribeToTypeChanges() { + this.limitForm.controls.type.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + const axisValueControl = this.limitForm.get('value'); + const axisEntityAliasControl = this.limitForm.get('entityAlias'); + if (axisValueControl) { + axisValueControl.setValue(null, { emitEvent: true }); + } + if (axisEntityAliasControl) { + axisEntityAliasControl.setValue(null, { emitEvent: true }); + } + }); + } + + private updateView(value: AxisLimitConfig) { + if (!isEqual(this.modelValue, value)) { + this.modelValue = value; + this.propagateChanges(value); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component.html index a462c92c03..bd6e8e888a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component.html @@ -19,6 +19,9 @@
{{ panelTitle }}
+
+
+
widgets.chart.chart-axis.scale-limits
+
+
+
widgets.chart.chart-axis.limit
+
widgets.chart.chart-axis.source
+
widgets.chart.chart-axis.key-value
+
+
+ + + + + +
+
+
+
-
-
-
widgets.chart.chart-axis.scale
-
-
widgets.chart.chart-axis.scale-min
- - - -
widgets.chart.chart-axis.scale-max
- - - -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts index 40f2a96c69..4bf7a78f98 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -17,9 +17,11 @@ import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, + NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup, + ValidationErrors, Validator, Validators } from '@angular/forms'; import { @@ -32,6 +34,9 @@ import { merge } from 'rxjs'; import { coerceBoolean } from '@shared/decorators/coercion'; import { WidgetService } from '@core/http/widget.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { IAliasController } from '@app/core/public-api'; +import { Datasource } from '@app/shared/public-api'; +import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; @Component({ selector: 'tb-time-series-chart-axis-settings', @@ -42,10 +47,15 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TimeSeriesChartAxisSettingsComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TimeSeriesChartAxisSettingsComponent), + multi: true } ] }) -export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValueAccessor { +export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValueAccessor, Validator { @Input() @coerceBoolean() @@ -61,6 +71,15 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu defaultXAxisTicksFormat = defaultXAxisTicksFormat; + @Input() + aliasController: IAliasController; + + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + @Input() disabled: boolean; @@ -147,6 +166,15 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu registerOnTouched(_fn: any): void { } + validate(): ValidationErrors | null { + return this.axisSettingsFormGroup.valid ? null : { + axisSettings: { + valid: false, + errors: this.getFormErrors() + } + }; + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { @@ -222,6 +250,17 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu } } + private getFormErrors(): any { + const errors: any = {}; + Object.keys(this.axisSettingsFormGroup.controls).forEach(key => { + const control = this.axisSettingsFormGroup.get(key); + if (control && control.errors) { + errors[key] = control.errors; + } + }); + return errors; + } + private updateModel() { this.modelValue = this.axisSettingsFormGroup.getRawValue(); this.propagateChange(this.modelValue); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts index 12ac24f9e3..4af0f5f613 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts @@ -47,6 +47,9 @@ import { mergeDeep } from '@core/utils'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { coerceBoolean } from '@shared/decorators/coercion'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { IAliasController } from '@app/core/public-api'; +import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; +import { Datasource } from '@app/shared/public-api'; @Component({ selector: 'tb-time-series-chart-y-axes-panel', @@ -68,6 +71,15 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; }) export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, OnInit, Validator { + @Input() + aliasController: IAliasController; + + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + @Input() disabled: boolean; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts index ebc24ddd22..1857852afe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts @@ -42,6 +42,7 @@ import { import { deepClone } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { TimeSeriesChartYAxesPanelComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component'; @Component({ selector: 'tb-time-series-chart-y-axis-row', @@ -85,6 +86,7 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O constructor(private fb: UntypedFormBuilder, private translate: TranslateService, private popoverService: TbPopoverService, + private timeSeriesChartYAxesPanel: TimeSeriesChartYAxesPanelComponent, private renderer: Renderer2, private viewContainerRef: ViewContainerRef, private cd: ChangeDetectorRef, @@ -165,7 +167,10 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O axisType: 'yAxis', panelTitle: this.translate.instant('widgets.time-series-chart.axis.y-axis-settings'), axisSettings: deepClone(this.modelValue), - advanced: this.advanced + advanced: this.advanced, + aliasController: this.timeSeriesChartYAxesPanel.aliasController, + dataKeyCallbacks: this.timeSeriesChartYAxesPanel.dataKeyCallbacks, + datasource: this.timeSeriesChartYAxesPanel.datasource }, isModal: true }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 233b202684..385bc55495 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -267,6 +267,7 @@ import { import { ShapeFillStripeSettingsPanelComponent } from '@home/components/widget/lib/settings/common/map/shape-fill-stripe-settings-panel.component'; +import { AxisScaleRowComponent } from './axis-scale-row.component'; @NgModule({ declarations: [ @@ -372,7 +373,8 @@ import { DataKeysComponent, DataKeyConfigDialogComponent, DataKeyConfigComponent, - WidgetSettingsComponent + WidgetSettingsComponent, + AxisScaleRowComponent ], imports: [ CommonModule, @@ -453,7 +455,8 @@ import { DataKeysComponent, DataKeyConfigDialogComponent, DataKeyConfigComponent, - WidgetSettingsComponent + WidgetSettingsComponent, + AxisScaleRowComponent ], providers: [ ColorSettingsComponentService, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 061ce65f0e..25eaa16793 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7638,6 +7638,14 @@ "update-animation-delay": "Update animation delay" }, "chart-axis": { + "limit":"Limit", + "source": "Source", + "key-value": "Key / Value", + "value-required": "Value is required.", + "entity-key-required": "Entity key is required.", + "key-required": "Key is required.", + "scale-limits": "Scale limits", + "scale-appearance": "Scale appearance", "scale": "Scale", "scale-min": "min", "scale-max": "max", diff --git a/ui-ngx/tailwind.config.js b/ui-ngx/tailwind.config.js index d675da4cda..bc516ddf40 100644 --- a/ui-ngx/tailwind.config.js +++ b/ui-ngx/tailwind.config.js @@ -84,7 +84,8 @@ module.exports = { '25': '6.25rem', '37.5': '9.375rem', '62.5': '15.625rem', - '72.5': '18.125rem' + '72.5': '18.125rem', + '40%': '40%' }, flex: { full: '1 1 100%' From ba6d84c40bebe4b16cb2381420de506654d7d61d Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 3 Dec 2025 17:32:21 +0200 Subject: [PATCH 2/8] fixed latest limit type display --- .../chart/bar-chart-basic-config.component.ts | 32 +-- .../latest-chart-basic-config.component.html | 132 ++++------ ...polar-area-chart-basic-config.component.ts | 32 +-- .../lib/chart/bar-chart-widget.models.ts | 5 +- .../widget/lib/chart/bars-chart.models.ts | 5 +- .../components/widget/lib/chart/bars-chart.ts | 225 +----------------- .../widget/lib/chart/latest-chart.ts | 1 + .../lib/chart/time-series-chart.models.ts | 12 +- .../widget/lib/chart/time-series-chart.ts | 133 +++++------ .../bar-chart-widget-settings.component.ts | 30 --- ...atest-chart-widget-settings.component.html | 94 +++----- .../latest-chart-widget-settings.component.ts | 10 - ...ar-area-chart-widget-settings.component.ts | 22 +- .../common/axis-scale-row.component.html | 4 +- .../common/axis-scale-row.component.ts | 129 +++++----- ...ime-series-chart-y-axes-panel.component.ts | 104 +++++++- 16 files changed, 328 insertions(+), 642 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts index a58ddd8302..4c7da10bc6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts @@ -29,8 +29,6 @@ import { import { LatestChartBasicConfigComponent } from '@home/components/widget/config/basic/chart/latest-chart-basic-config.component'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; -import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-bar-chart-basic-config', @@ -43,7 +41,7 @@ export class BarChartBasicConfigComponent extends LatestChartBasicConfigComponen barChartConfigTemplate: TemplateRef; predefinedValues = widgetTitleAutocompleteValues; - + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, protected fb: UntypedFormBuilder) { @@ -81,32 +79,4 @@ export class BarChartBasicConfigComponent extends LatestChartBasicConfigComponen this.widgetConfig.config.settings.axisTickLabelFont = config.axisTickLabelFont; this.widgetConfig.config.settings.axisTickLabelColor = config.axisTickLabelColor; } - - protected onConfigSet(configData: WidgetConfigComponentData) { - configData.config.settings.axisMin = this.normalizeAxisLimit(configData.config.settings.axisMin); - configData.config.settings.axisMax = this.normalizeAxisLimit(configData.config.settings.axisMax); - super.onConfigSet(configData); - } - - private normalizeAxisLimit(limit: any): AxisLimitConfig { - if (limit && typeof limit === 'object' && 'type' in limit) { - return { - type: limit.type || ValueSourceType.constant, - value: limit.value ?? null, - entityAlias: limit.entityAlias ?? null - }; - } - if (typeof limit === 'number') { - return { - type: ValueSourceType.constant, - value: limit, - entityAlias: null - }; - } - return { - type: ValueSourceType.constant, - value: null, - entityAlias: null - }; - } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html index 27c173bd17..d72a8d6083 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html @@ -21,27 +21,27 @@ formControlName="timewindowConfig"> + [configMode]="basicMode" + hideDatasourceLabel + hideDataKeys + forceSingleDatasource + formControlName="datasources"> + panelTitle="{{ 'widgets.chart.series' | translate }}" + addKeyTitle="{{ 'widgets.chart.add-series' | translate }}" + keySettingsTitle="{{ 'widgets.chart.series-settings' | translate }}" + removeKeyTitle="{{ 'widgets.chart.remove-series' | translate }}" + noKeysText="{{ 'widgets.chart.no-series' | translate }}" + requiredKeysText="{{ 'widgets.chart.no-series-error' | translate }}" + hideUnits + hideDecimals + hideDataKeyUnits + hideDataKeyDecimals + [datasourceType]="datasource?.type" + [deviceId]="datasource?.deviceId" + [entityAliasId]="datasource?.entityAliasId" + formControlName="series">
widget-config.appearance
@@ -210,7 +210,7 @@
+ formControlName="animation">
widget-config.card-appearance
@@ -239,7 +239,7 @@
+ formControlName="actions"> @@ -346,15 +346,25 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + [series]="false">
widgets.bar-chart.bar-axis
-
widgets.chart.chart-axis.scale-appearance
+
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-min
+ + + +
widgets.chart.chart-axis.scale-max
+ + +
-
-
widgets.chart.chart-axis.scale-limits
-
-
-
widgets.chart.chart-axis.limit
-
widgets.chart.chart-axis.source
-
widgets.chart.chart-axis.key-value
-
-
- - - - - -
-
-
@@ -402,16 +385,26 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + pieLabelPosition + [series]="false">
widgets.polar-area-chart.polar-axis
-
widgets.chart.chart-axis.scale-appearance
+
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-min
+ + + +
widgets.chart.chart-axis.scale-max
+ + +
-
-
widgets.chart.chart-axis.scale-limits
-
-
-
widgets.chart.chart-axis.limit
-
widgets.chart.chart-axis.source
-
widgets.chart.chart-axis.key-value
-
-
- - - - - -
-
-
@@ -542,7 +508,7 @@
+ formControlName="fillAreaSettings">
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts index d29af73b75..f60f8d2417 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts @@ -29,8 +29,6 @@ import { import { LatestChartBasicConfigComponent } from '@home/components/widget/config/basic/chart/latest-chart-basic-config.component'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; -import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-polar-area-chart-basic-config', @@ -43,7 +41,7 @@ export class PolarAreaChartBasicConfigComponent extends LatestChartBasicConfigCo polarAreaChartConfigTemplate: TemplateRef; predefinedValues = widgetTitleAutocompleteValues; - + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, protected fb: UntypedFormBuilder) { @@ -83,32 +81,4 @@ export class PolarAreaChartBasicConfigComponent extends LatestChartBasicConfigCo this.widgetConfig.config.settings.axisTickLabelColor = config.axisTickLabelColor; this.widgetConfig.config.settings.angleAxisStartAngle = config.angleAxisStartAngle; } - - protected onConfigSet(configData: WidgetConfigComponentData) { - configData.config.settings.axisMin = this.normalizeAxisLimit(configData.config.settings.axisMin); - configData.config.settings.axisMax = this.normalizeAxisLimit(configData.config.settings.axisMax); - super.onConfigSet(configData); - } - - private normalizeAxisLimit(limit: any): AxisLimitConfig { - if (limit && typeof limit === 'object' && 'type' in limit) { - return { - type: limit.type || ValueSourceType.constant, - value: limit.value ?? null, - entityAlias: limit.entityAlias ?? null - }; - } - if (typeof limit === 'number') { - return { - type: ValueSourceType.constant, - value: limit, - entityAlias: null - }; - } - return { - type: ValueSourceType.constant, - value: null, - entityAlias: null - }; - } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts index 0fd3b027ab..3f76eb18cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts @@ -31,11 +31,10 @@ import { ChartBarSettings, chartColorScheme } from '@home/components/widget/lib/chart/chart.models'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; export interface BarChartWidgetSettings extends LatestChartWidgetSettings { - axisMin?: number | string | AxisLimitConfig; - axisMax?: number | string | AxisLimitConfig; + axisMin?: number; + axisMax?: number; axisTickLabelFont: Font; axisTickLabelColor: string; barSettings: ChartBarSettings; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts index 860f4da5b9..2808d9f7e5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts @@ -24,12 +24,11 @@ import { chartColorScheme } from '@home/components/widget/lib/chart/chart.models'; import { Font } from '@shared/models/widget-settings.models'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; export interface BarsChartSettings extends LatestChartSettings { polar: boolean; - axisMin?: number | string | AxisLimitConfig; - axisMax?: number | string | AxisLimitConfig; + axisMin?: number | string; + axisMax?: number | string; axisTickLabelFont: Font; axisTickLabelColor: string; angleAxisStartAngle?: number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts index 61adae9c20..92df6dd363 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts @@ -25,7 +25,7 @@ import { ComponentStyle } from '@shared/models/widget-settings.models'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; import { BarDataItemOption, BarSeriesLabelOption } from 'echarts/types/src/chart/bar/BarSeries'; -import { isDefinedAndNotNull, isNumber, isString } from '@core/utils'; +import { isDefinedAndNotNull } from '@core/utils'; import { ChartFillType, ChartLabelPosition, @@ -35,19 +35,9 @@ import { } from '@home/components/widget/lib/chart/chart.models'; import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes'; import { RadiusAxisOption, YAXisOption } from 'echarts/types/dist/shared'; -import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; -import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { ValueSourceType } from '@shared/models/widget-settings.models'; -import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; export class TbBarsChart extends TbLatestChart { - private dynamicAxisMin: number | string = null; - private dynamicAxisMax: number | string = null; - private minLatestDataKey: DataKey = null; - private maxLatestDataKey: DataKey = null; - constructor(ctx: WidgetContext, inputSettings: DeepPartial, chartElement: HTMLElement, @@ -62,212 +52,6 @@ export class TbBarsChart extends TbLatestChart { return barsChartDefaultSettings; } - protected initSettings() { - super.initSettings(); - this.setupAxisLimits(); - } - - private setupAxisLimits(): void { - const axisLimitDatasources: Datasource[] = []; - - if (isDefinedAndNotNull(this.settings.axisMin)) { - this.processAxisLimit(this.settings.axisMin, 'min', axisLimitDatasources); - } - if (isDefinedAndNotNull(this.settings.axisMax)) { - this.processAxisLimit(this.settings.axisMax, 'max', axisLimitDatasources); - } - - this.subscribeForAxisLimits(axisLimitDatasources); - } - - private processAxisLimit( - limit: any, - limitType: 'min' | 'max', - axisLimitDatasources: Datasource[] - ): void { - if (limit && typeof limit === 'object' && 'type' in limit) { - const axisLimit = limit as AxisLimitConfig; - - if (axisLimit.type === ValueSourceType.latestKey) { - let latestDataKey: DataKey = null; - if (this.ctx.datasources.length) { - for (const datasource of this.ctx.datasources) { - latestDataKey = datasource.latestDataKeys?.find(d => - (d.type === DataKeyType.function && d.label === (axisLimit.value as DataKey).label) || - (d.type !== DataKeyType.function && d.name === (axisLimit.value as DataKey).name && - d.type === (axisLimit.value as DataKey).type)); - if (latestDataKey) { - break; - } - } - } - - if (latestDataKey) { - if (limitType === 'min') { - this.minLatestDataKey = latestDataKey; - } else { - this.maxLatestDataKey = latestDataKey; - } - } - } else if (axisLimit.type === ValueSourceType.entity) { - const entityAliasId = this.ctx.aliasController.getEntityAliasId(axisLimit.entityAlias); - if (entityAliasId) { - let datasource = axisLimitDatasources.find(d => d.entityAliasId === entityAliasId); - const entityDataKey: DataKey = { - type: (axisLimit.value as DataKey).type, - name: (axisLimit.value as DataKey).name, - label: (axisLimit.value as DataKey).name, - settings: { - axisLimit: limitType - } - }; - - if (datasource) { - datasource.dataKeys.push(entityDataKey); - } else { - datasource = { - type: DatasourceType.entity, - name: axisLimit.entityAlias, - aliasName: axisLimit.entityAlias, - entityAliasId, - dataKeys: [entityDataKey] - }; - axisLimitDatasources.push(datasource); - } - } - } else if (axisLimit.type === ValueSourceType.constant) { - const value = axisLimit.value as number; - if (limitType === 'min') { - this.dynamicAxisMin = value; - } else { - this.dynamicAxisMax = value; - } - } - } else if (typeof limit === 'number' || typeof limit === 'string') { - if (limitType === 'min') { - this.dynamicAxisMin = limit; - } else { - this.dynamicAxisMax = limit; - } - } - } - - private subscribeForAxisLimits(datasources: Datasource[]) { - if (datasources.length) { - const axisLimitsSubscriptionOptions: WidgetSubscriptionOptions = { - datasources, - useDashboardTimewindow: false, - type: widgetType.latest, - callbacks: { - onDataUpdated: (subscription) => { - let update = false; - if (subscription.data) { - for (const data of subscription.data) { - const limitType = data.dataKey.settings.axisLimit as ('min' | 'max'); - if (data.data[0]) { - const value = this.parseAxisLimitData(data.data[0][1]); - if (limitType === 'min') { - if (this.dynamicAxisMin !== value) { - this.dynamicAxisMin = value; - update = true; - } - } else { - if (this.dynamicAxisMax !== value) { - this.dynamicAxisMax = value; - update = true; - } - } - } - } - } - if (this.latestChart && update) { - this.updateAxisLimits(); - } - } - } - }; - this.ctx.subscriptionApi.createSubscription(axisLimitsSubscriptionOptions, true).subscribe(); - } - } - - private parseAxisLimitData(data: any): number | null { - let value: number; - if (isDefinedAndNotNull(data)) { - if (isNumber(data)) { - value = data; - } else if (isString(data)) { - value = Number(data); - } - } - if (isDefinedAndNotNull(value) && !isNaN(value)) { - return value; - } - return null; - } - - private updateAxisLimitsFromLatest(): boolean { - let update = false; - - if (this.ctx.latestData) { - if (this.minLatestDataKey) { - const data = this.ctx.latestData.find(d => d.dataKey === this.minLatestDataKey); - if (data?.data[0]) { - const value = this.parseAxisLimitData(data.data[0][1]); - if (this.dynamicAxisMin !== value) { - this.dynamicAxisMin = value; - update = true; - } - } - } - - if (this.maxLatestDataKey) { - const data = this.ctx.latestData.find(d => d.dataKey === this.maxLatestDataKey); - if (data?.data[0]) { - const value = this.parseAxisLimitData(data.data[0][1]); - if (this.dynamicAxisMax !== value) { - this.dynamicAxisMax = value; - update = true; - } - } - } - } - return update; - } - - private updateAxisLimits(): void { - if (this.latestChart && !this.latestChart.isDisposed()) { - const axisTickLabelStyle = createChartTextStyle(this.settings.axisTickLabelFont, - this.settings.axisTickLabelColor, false, 'axis.tickLabel'); - const valueAxis: ValueAxisBaseOption = { - type: 'value', - min: this.dynamicAxisMin, - max: this.dynamicAxisMax, - axisLabel: { - color: axisTickLabelStyle.color, - fontStyle: axisTickLabelStyle.fontStyle, - fontWeight: axisTickLabelStyle.fontWeight, - fontFamily: axisTickLabelStyle.fontFamily, - fontSize: axisTickLabelStyle.fontSize, - formatter: (value: any) => this.valueFormatter.format(value) - } - }; - - if (this.settings.polar) { - this.latestChartOption.radiusAxis = valueAxis as RadiusAxisOption; - } else { - this.latestChartOption.yAxis = valueAxis as YAXisOption; - } - - this.latestChart.setOption(this.latestChartOption); - } - } - - public latestUpdated() { - if (this.updateAxisLimitsFromLatest()) { - this.updateAxisLimits(); - } - } - protected prepareLatestChartOption() { let labelStyle: ComponentStyle = {}; if (this.settings.barSettings.showLabel) { @@ -305,12 +89,10 @@ export class TbBarsChart extends TbLatestChart { const axisTickLabelStyle = createChartTextStyle(this.settings.axisTickLabelFont, this.settings.axisTickLabelColor, false, 'axis.tickLabel'); - const minValue = isDefinedAndNotNull(this.dynamicAxisMin) ? this.dynamicAxisMin : undefined; - const maxValue = isDefinedAndNotNull(this.dynamicAxisMax) ? this.dynamicAxisMax : undefined; const valueAxis: ValueAxisBaseOption = { type: 'value', - min: minValue, - max: maxValue, + min: this.settings.axisMin, + max: this.settings.axisMax, axisLabel: { color: axisTickLabelStyle.color, fontStyle: axisTickLabelStyle.fontStyle, @@ -320,7 +102,6 @@ export class TbBarsChart extends TbLatestChart { formatter: (value: any) => this.valueFormatter.format(value) } }; - if (this.settings.polar) { this.latestChartOption.polar = { radius: '100%' diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts index 9e525cd794..5911a60215 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts @@ -142,6 +142,7 @@ export abstract class TbLatestChart { public update(): void { for (const dsData of this.ctx.data) { + console.log("this.ctx.data",this.ctx.data) let value = 0; const tsValue = dsData.data[0]; const dataItem = this.dataItems.find(item => item.dataKey === dsData.dataKey); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index e5987e9664..5585496ede 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -381,18 +381,12 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting decimals?: number; interval?: number; splitNumber?: number; - min?: number | string | AxisLimitConfig; - max?: number | string | AxisLimitConfig; + min?: string | ValueSourceConfig; + max?: string | ValueSourceConfig; ticksGenerator?: TimeSeriesChartTicksGenerator | string; ticksFormatter?: TimeSeriesChartTicksFormatter | string; } -export interface AxisLimitConfig { - entityAlias: string; - type: ValueSourceType; - value: number | DataKey; -} - export const timeSeriesChartYAxisValid = (axis: TimeSeriesChartYAxisSettings): boolean => !(!axis.id || isUndefinedOrNull(axis.order)); @@ -873,8 +867,6 @@ export interface TimeSeriesChartAxis { id: string; settings: TimeSeriesChartAxisSettings; option: CartesianAxisOption; - dynamicMin?: string| number; - dynamicMax?: string| number; minLatestDataKey?: DataKey; maxLatestDataKey?: DataKey; unitConvertor?: (value: number) => number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 7954352bb4..d429438f43 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -17,7 +17,6 @@ import { WidgetContext } from '@home/models/widget-component.models'; import { adjustTimeAxisExtentToData, - AxisLimitConfig, calculateThresholdsOffset, createTimeSeriesVisualMapOption, createTimeSeriesXAxis, @@ -55,7 +54,7 @@ import { getFocusedSeriesIndex, measureAxisNameSize } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { DateFormatProcessor, ValueSourceType } from '@shared/models/widget-settings.models'; +import { DateFormatProcessor, ValueSourceConfig, ValueSourceType } from '@shared/models/widget-settings.models'; import { formattedDataFormDatasourceData, formatValue, @@ -289,15 +288,36 @@ export class TbTimeSeriesChart { } } } - } - if (this.updateAxisLimitsFromLatest()) { - update = true; + + for (const yAxis of this.yAxisList) { + const minType = (yAxis.settings.min as ValueSourceConfig).type; + const maxType = (yAxis.settings.max as ValueSourceConfig).type; + if (minType === ValueSourceType.latestKey && yAxis.minLatestDataKey) { + const data = this.ctx.latestData.find(d => d.dataKey === yAxis.minLatestDataKey); + if (data?.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); + if (yAxis.option.min !== value) { + yAxis.option.min = value; + update = true; + } + } + } + + if (maxType === ValueSourceType.latestKey && yAxis.maxLatestDataKey) { + const data = this.ctx.latestData.find(d => d.dataKey === yAxis.maxLatestDataKey); + if (data?.data[0]) { + const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); + if (yAxis.option.max !== value) { + yAxis.option.max = value; + update = true; + } + } + } + } } if (this.timeSeriesChart && update) { this.updateSeriesData(); - if (this.updateAxisLimitsFromLatest()) { - this.updateAxisLimits(); - } + this.updateAxisLimits(); } } @@ -574,20 +594,17 @@ export class TbTimeSeriesChart { } const yAxis = createTimeSeriesYAxis(unitSymbol, decimals, axisSettings, this.ctx.utilsService, this.darkMode, unitConvertor); if (isDefinedAndNotNull(axisSettings.min)) { - this.processAxisLimit(axisSettings.min, 'min', yAxis, axisLimitDatasources, unitConvertor); + this.processYAxisLimit(axisSettings.min, 'min', yAxis, axisLimitDatasources, unitConvertor); } if (isDefinedAndNotNull(axisSettings.max)) { - this.processAxisLimit(axisSettings.max, 'max', yAxis, axisLimitDatasources, unitConvertor); - } - if (yAxis.minLatestDataKey || yAxis.maxLatestDataKey) { - yAxis.unitConvertor = unitConvertor; + this.processYAxisLimit(axisSettings.max, 'max', yAxis, axisLimitDatasources, unitConvertor); } this.yAxisList.push(yAxis); } this.subscribeForAxisLimits(axisLimitDatasources); } - private processAxisLimit( + private processYAxisLimit( limit: any, limitType: 'min' | 'max', yAxis: TimeSeriesChartYAxis, @@ -595,15 +612,15 @@ export class TbTimeSeriesChart { unitConvertor?: (value: number) => number ): void { if (limit && typeof limit === 'object' && 'type' in limit) { - const axisLimit = limit as AxisLimitConfig; + const axisLimit = limit as ValueSourceConfig; if (axisLimit.type === ValueSourceType.latestKey) { let latestDataKey: DataKey = null; if (this.ctx.datasources.length) { for (const datasource of this.ctx.datasources) { latestDataKey = datasource.latestDataKeys?.find(d => - (d.type === DataKeyType.function && d.label === (axisLimit.value as DataKey).label) || - (d.type !== DataKeyType.function && d.name === (axisLimit.value as DataKey).name && - d.type === (axisLimit.value as DataKey).type)); + (d.type === DataKeyType.function && d.label === axisLimit.latestKey) || + (d.type !== DataKeyType.function && d.name === axisLimit.latestKey && + d.type === axisLimit.latestKeyType)); if (latestDataKey) { break; } @@ -621,9 +638,9 @@ export class TbTimeSeriesChart { if (entityAliasId) { let datasource = axisLimitDatasources.find(d => d.entityAliasId === entityAliasId); const entityDataKey: DataKey = { - type: (axisLimit.value as DataKey).type, - name: (axisLimit.value as DataKey).name, - label: (axisLimit.value as DataKey).name, + type: limit.entityKeyType, + name: limit.entityKey, + label: limit.entityKey, settings: { yAxisId: yAxis.id, axisLimit: limitType @@ -643,27 +660,14 @@ export class TbTimeSeriesChart { } } } else if (axisLimit.type === ValueSourceType.constant) { - const value = unitConvertor ? unitConvertor(axisLimit.value as number) : axisLimit.value as number; + const value = unitConvertor ? unitConvertor(axisLimit.value) : axisLimit.value; if (limitType === 'min') { - yAxis.dynamicMin = value; yAxis.option.min = value; } else { - yAxis.dynamicMax = value; yAxis.option.max = value; } } - } else if (typeof limit === 'number' || typeof limit === 'string') { - let value = limit; - if (typeof value === 'number' && unitConvertor) { - value = unitConvertor(value); - } - if (limitType === 'min') { - yAxis.dynamicMin = value; - yAxis.option.min = value; - } else { - yAxis.dynamicMax = value; - yAxis.option.max = value; - } + return; } } @@ -738,25 +742,30 @@ export class TbTimeSeriesChart { const limitType = data.dataKey.settings.axisLimit as ('min' | 'max'); if (data.data[0]) { const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); + if (isDefinedAndNotNull(value)) { if (limitType === 'min') { - yAxis.dynamicMin = value; - yAxis.option.min = value; + if (yAxis.option.min !== value) { + yAxis.option.min = value; + update = true; + } } else { - yAxis.dynamicMax = value; - yAxis.option.max = value; + if (yAxis.option.max !== value) { + yAxis.option.max = value; + update = true; + } } - update = true; } } } } } - if (this.timeSeriesChart && update) { - this.updateAxisLimits(); - } + } + if (this.timeSeriesChart && update) { + this.updateAxisLimits(); } } - }; + } + }; this.ctx.subscriptionApi.createSubscription(axisLimitsSubscriptionOptions, true).subscribe(); } } @@ -853,40 +862,6 @@ export class TbTimeSeriesChart { } } - private updateAxisLimitsFromLatest(): boolean { - let update = false; - - if (this.ctx.latestData) { - for (const yAxis of this.yAxisList) { - if (yAxis.minLatestDataKey) { - const data = this.ctx.latestData.find(d => d.dataKey === yAxis.minLatestDataKey); - if (data?.data[0]) { - const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); - - if (yAxis.dynamicMin !== value) { - yAxis.dynamicMin = value; - yAxis.option.min = value; - update = true; - } - } - } - - if (yAxis.maxLatestDataKey) { - const data = this.ctx.latestData.find(d => d.dataKey === yAxis.maxLatestDataKey); - if (data?.data[0]) { - const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); - if (yAxis.dynamicMax !== value) { - yAxis.dynamicMax = value; - yAxis.option.max = value; - update = true; - } - } - } - } - } - return update; - } - private parseAxisLimitData = (data: any, unitConvertor?: (value: number) => number): number => { let value: number; if (isDefinedAndNotNull(data)) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts index b1734ff45a..8826e9afda 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts @@ -26,8 +26,6 @@ import { import { LatestChartWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/latest-chart-widget-settings.component'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; -import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-bar-chart-widget-settings', @@ -59,32 +57,4 @@ export class BarChartWidgetSettingsComponent extends LatestChartWidgetSettingsCo latestChartWidgetSettingsForm.addControl('axisTickLabelFont', this.fb.control(settings.axisTickLabelFont, [])); latestChartWidgetSettingsForm.addControl('axisTickLabelColor', this.fb.control(settings.axisTickLabelColor, [])); } - - protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { - settings.axisMin = this.normalizeAxisLimit(settings.axisMin); - settings.axisMax = this.normalizeAxisLimit(settings.axisMax); - return super.prepareInputSettings(settings); - } - - private normalizeAxisLimit(limit: any): AxisLimitConfig { - if (limit && typeof limit === 'object' && 'type' in limit) { - return { - type: limit.type || ValueSourceType.constant, - value: limit.value ?? null, - entityAlias: limit.entityAlias ?? null - }; - } - if (typeof limit === 'number') { - return { - type: ValueSourceType.constant, - value: limit, - entityAlias: null - }; - } - return { - type: ValueSourceType.constant, - value: null, - entityAlias: null - }; - } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html index 6bbb996e93..3f1e96ca84 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html @@ -120,7 +120,7 @@
+ formControlName="animation">
widget-config.card-appearance
@@ -256,15 +256,25 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + [series]="false">
widgets.bar-chart.bar-axis
-
widgets.chart.chart-axis.scale-appearance
+
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-min
+ + + +
widgets.chart.chart-axis.scale-max
+ + +
-
-
widgets.chart.chart-axis.scale-limits
-
-
-
widgets.chart.chart-axis.limit
-
widgets.chart.chart-axis.source
-
widgets.chart.chart-axis.key-value
-
-
-
- - - - - -
-
-
-
@@ -322,16 +303,26 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + pieLabelPosition + [series]="false">
widgets.polar-area-chart.polar-axis
-
widgets.chart.chart-axis.scale-appearance
+
widgets.chart.chart-axis.scale
+
widgets.chart.chart-axis.scale-min
+ + + +
widgets.chart.chart-axis.scale-max
+ + +
-
-
widgets.chart.chart-axis.scale-limits
-
-
-
widgets.chart.chart-axis.limit
-
widgets.chart.chart-axis.source
-
widgets.chart.chart-axis.key-value
-
-
- - - - - -
-
-
@@ -470,7 +434,7 @@ + formControlName="fillAreaSettings">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts index fadee36370..ff7641bf1a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.ts @@ -22,7 +22,6 @@ import { LatestChartWidgetSettings } from '@home/components/widget/lib/chart/latest-chart.models'; import { - Datasource, legendPositions, legendPositionTranslationMap, WidgetSettings, @@ -104,15 +103,6 @@ export abstract class LatestChartWidgetSettingsComponent, protected fb: UntypedFormBuilder) { super(store); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts index 61c533a377..355dc55207 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts @@ -26,8 +26,7 @@ import { import { LatestChartWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/latest-chart-widget-settings.component'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; -import { ValueSourceType } from '@shared/models/widget-settings.models'; +import { ValueSourceConfig, ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-polar-area-chart-widget-settings', @@ -66,25 +65,20 @@ export class PolarAreaChartWidgetSettingsComponent extends LatestChartWidgetSett return super.prepareInputSettings(settings); } - private normalizeAxisLimit(limit: any): AxisLimitConfig { - if (limit && typeof limit === 'object' && 'type' in limit) { + private normalizeAxisLimit(limit: any): ValueSourceConfig { + if (!limit) { return { - type: limit.type || ValueSourceType.constant, - value: limit.value ?? null, - entityAlias: limit.entityAlias ?? null + type: ValueSourceType.constant, + value: null, + entityAlias: null }; - } - if (typeof limit === 'number') { + } else if (typeof limit === 'number') { return { type: ValueSourceType.constant, value: limit, entityAlias: null }; } - return { - type: ValueSourceType.constant, - value: null, - entityAlias: null - }; + return limit; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html index eed985187d..37c2838a21 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html @@ -53,7 +53,7 @@ [dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" [callbacks]="callbacks" [editable]="false" - formControlName="value"> + [formControl]="latestKeyFormControl"> + [formControl]="entityKeyFormControl">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts index b8103dd008..165857eac3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts @@ -18,18 +18,22 @@ import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core' import { ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, - UntypedFormBuilder, + UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, ValidationErrors, Validator, Validators, } from '@angular/forms'; -import { ValueSourceType, ValueSourceTypes, ValueSourceTypeTranslation } from '@shared/models/widget-settings.models'; +import { + ValueSourceConfig, + ValueSourceType, + ValueSourceTypes, + ValueSourceTypeTranslation +} from '@shared/models/widget-settings.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; -import { Datasource, DatasourceType, } from '@shared/models/widget.models'; +import { DataKey, Datasource, DatasourceType, } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { IAliasController } from '@core/api/widget-api.models'; import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; -import { isEqual } from '@core/utils'; +import { merge } from 'rxjs'; @Component({ selector: 'tb-axis-scale-row', @@ -77,9 +81,13 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali limitForm: UntypedFormGroup; + latestKeyFormControl: UntypedFormControl; + + entityKeyFormControl: UntypedFormControl; + private propagateChanges: (value: any) => void = () => {}; - private modelValue: AxisLimitConfig | null = null; + private modelValue: ValueSourceConfig | null = null; constructor(private fb: UntypedFormBuilder, private destroyRef: DestroyRef) { @@ -91,42 +99,44 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali value: [null], entityAlias: [null] }); + this.latestKeyFormControl = this.fb.control(null, [Validators.required]); + this.entityKeyFormControl = this.fb.control(null, [Validators.required]); this.subscribeToTypeChanges(); - - this.limitForm.valueChanges - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(value => { + merge( + this.latestKeyFormControl.valueChanges, + this.entityKeyFormControl.valueChanges, + this.limitForm.valueChanges + ).pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { this.updateValidators(); - this.updateView(value); + this.updateModel(); }); } - writeValue(value: AxisLimitConfig) { + writeValue(value: ValueSourceConfig) { + console.log("value", value); this.modelValue = value; - - if (!this.limitForm) { - return; - } - - if (value) { - this.limitForm.patchValue({ - type: value.type ?? ValueSourceType.constant, - value: value.value ?? null, - entityAlias: value.entityAlias ?? null - }, - { emitEvent: false } - ); - } else { - this.limitForm.patchValue({ - type: ValueSourceType.constant, - value: null, - entityAlias: null - }, - { emitEvent: false } - ); + this.limitForm.patchValue( + { + type: value.type || ValueSourceType.constant, + value: value.value, + entityAlias: value.entityAlias, + }, {emitEvent: false} + ); + if (value.type === ValueSourceType.latestKey) { + this.latestKeyFormControl.patchValue({ + type: value.latestKeyType, + name: value.latestKey + }, {emitEvent: false}); + } else if (value.type === ValueSourceType.entity) { + this.entityKeyFormControl.patchValue({ + type: value.entityKeyType, + name: value.entityKey + }, {emitEvent: false}); } this.updateValidators(); + this.limitForm.markAllAsTouched(); } registerOnChange(fn: any) { @@ -158,28 +168,22 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali private updateValidators() { const axisTypeControl = this.limitForm.get('type'); - const axisValueControl = this.limitForm.get('value'); - const axisEntityAliasControl = this.limitForm.get('entityAlias'); - - if (axisTypeControl && axisValueControl && axisEntityAliasControl) { + if (axisTypeControl && this.entityKeyFormControl && this.latestKeyFormControl) { const type = axisTypeControl.value; - if (type === ValueSourceType.latestKey || type === ValueSourceType.entity) { - axisValueControl.setValidators([Validators.required]); - } else { - axisValueControl.clearValidators(); - } - - if (type === ValueSourceType.entity) { - axisEntityAliasControl.setValidators([Validators.required]); + if (type === ValueSourceType.latestKey) { + this.latestKeyFormControl.setValidators([Validators.required]); + this.entityKeyFormControl.clearValidators(); + } else if (type === ValueSourceType.entity) { + this.latestKeyFormControl.clearValidators(); + this.entityKeyFormControl.setValidators([Validators.required]); } else { - axisEntityAliasControl.clearValidators(); + this.latestKeyFormControl.clearValidators(); + this.entityKeyFormControl.clearValidators(); } - - axisValueControl.updateValueAndValidity({ emitEvent: false }); - axisEntityAliasControl.updateValueAndValidity({ emitEvent: false }); + this.latestKeyFormControl.updateValueAndValidity({ emitEvent: false }); + this.entityKeyFormControl.updateValueAndValidity({ emitEvent: false }); } } - private subscribeToTypeChanges() { this.limitForm.controls.type.valueChanges .pipe(takeUntilDestroyed(this.destroyRef)) @@ -187,18 +191,31 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali const axisValueControl = this.limitForm.get('value'); const axisEntityAliasControl = this.limitForm.get('entityAlias'); if (axisValueControl) { - axisValueControl.setValue(null, { emitEvent: true }); + axisValueControl.setValue(null, { emitEvent: false }); } if (axisEntityAliasControl) { - axisEntityAliasControl.setValue(null, { emitEvent: true }); + axisEntityAliasControl.setValue(null, { emitEvent: false }); } + this.latestKeyFormControl.setValue(null, { emitEvent: false }); + this.entityKeyFormControl.setValue(null, { emitEvent: false }); + }); } - private updateView(value: AxisLimitConfig) { - if (!isEqual(this.modelValue, value)) { - this.modelValue = value; - this.propagateChanges(value); + private updateModel() { + const value = this.limitForm.value; + this.modelValue.type = value.type ?? ValueSourceType.constant; + this.modelValue.value = value?.value; + this.modelValue.entityAlias = value?.entityAlias; + if (value.type === ValueSourceType.latestKey) { + const latestKey: DataKey = this.latestKeyFormControl.value; + this.modelValue.latestKey = latestKey?.name; + this.modelValue.latestKeyType = (latestKey?.type as any); + } else if (value.type === ValueSourceType.entity) { + const entityKey: DataKey = this.entityKeyFormControl.value; + this.modelValue.entityKey = entityKey?.name; + this.modelValue.entityKeyType = (entityKey?.type as any); } + this.propagateChanges(this.modelValue); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts index 4af0f5f613..3159a27abd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts @@ -38,7 +38,8 @@ import { import { defaultTimeSeriesChartYAxisSettings, getNextTimeSeriesYAxisId, - TimeSeriesChartYAxes, TimeSeriesChartYAxisId, + TimeSeriesChartYAxes, + TimeSeriesChartYAxisId, TimeSeriesChartYAxisSettings, timeSeriesChartYAxisValid, timeSeriesChartYAxisValidator @@ -49,7 +50,7 @@ import { coerceBoolean } from '@shared/decorators/coercion'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { IAliasController } from '@app/core/public-api'; import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; -import { Datasource } from '@app/shared/public-api'; +import { DataKey, DataKeyType, Datasource, ValueSourceConfig, ValueSourceType } from '@app/shared/public-api'; @Component({ selector: 'tb-time-series-chart-y-axes-panel', @@ -125,6 +126,7 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, for (const axis of axes) { yAxes[axis.id] = axis; } + this.updateLatestDataKeys(Object.values(yAxes)); this.propagateChange(yAxes); } ); @@ -147,7 +149,7 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, } writeValue(value: TimeSeriesChartYAxes | undefined): void { - const yAxes: TimeSeriesChartYAxes = value || {}; + const yAxes: TimeSeriesChartYAxes = this.checkLatestDataKeys(value || {}); if (!yAxes.default) { yAxes.default = mergeDeep({} as TimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings, {id: 'default', order: 0} as TimeSeriesChartYAxisSettings); @@ -202,8 +204,104 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, private prepareAxesFormArray(axes: TimeSeriesChartYAxisSettings[]): UntypedFormArray { const axesControls: Array = []; axes.forEach((axis) => { + axis.min = this.normalizeAxisLimit(axis.min); + axis.max = this.normalizeAxisLimit(axis.max); axesControls.push(this.fb.control(axis, [timeSeriesChartYAxisValidator])); }); return this.fb.array(axesControls); } + + private checkLatestDataKeys(yAxes: TimeSeriesChartYAxes): TimeSeriesChartYAxes { + const latestKeys = this.datasource?.latestDataKeys || []; + const result: TimeSeriesChartYAxes = {}; + + for (const [id, axis] of Object.entries(yAxes)) { + + const minCfg = axis.min as unknown as ValueSourceConfig; + const maxCfg = axis.max as unknown as ValueSourceConfig; + + const minValid = + minCfg?.type !== ValueSourceType.latestKey || + latestKeys.some(k => this.isYAxisKey(k, minCfg)); + + const maxValid = + maxCfg?.type !== ValueSourceType.latestKey || + latestKeys.some(k => this.isYAxisKey(k, maxCfg)); + + if (minValid && maxValid) { + result[id] = axis; + } + } + + return result; + } + + private updateLatestDataKeys(yAxes: TimeSeriesChartYAxisSettings[]) { + if (this.datasource) { + let latestKeys = this.datasource.latestDataKeys; + if (!latestKeys) { + latestKeys = []; + this.datasource.latestDataKeys = latestKeys; + } + const existingYAxisKeys = latestKeys.filter(k => k.settings?.__yAxisMinKey === true || k.settings?.__yAxisMaxKey === true); + const foundYAxisKeys: DataKey[] = []; + + for(const yAxis of yAxes) { + const min = yAxis.min as ValueSourceConfig; + const max = yAxis.max as ValueSourceConfig; + if (min.type === ValueSourceType.latestKey) { + const found = existingYAxisKeys.find(k => this.isYAxisKey(k, min)); + if (!found) { + const newKey = this.dataKeyCallbacks.generateDataKey(min.latestKey, min.latestKeyType, + null, true, null); + newKey.settings.__yAxisMinKey = true; + latestKeys.push(newKey); + } else if (foundYAxisKeys.indexOf(found) === -1) { + foundYAxisKeys.push(found); + } + } + if (max.type === ValueSourceType.latestKey) { + const found = existingYAxisKeys.find(k => this.isYAxisKey(k, max)); + if (!found) { + const newKey = this.dataKeyCallbacks.generateDataKey(max.latestKey, max.latestKeyType, + null, true, null); + newKey.settings.__yAxisMaxKey = true; + latestKeys.push(newKey); + } else if (foundYAxisKeys.indexOf(found) === -1) { + foundYAxisKeys.push(found); + } + } + } + const toRemove = existingYAxisKeys.filter(k => foundYAxisKeys.indexOf(k) === -1); + for (const key of toRemove) { + const index = latestKeys.indexOf(key); + if (index > -1) { + latestKeys.splice(index, 1); + } + } + } + } + + private isYAxisKey(d: DataKey, limit: ValueSourceConfig): boolean { + return (d.type === DataKeyType.function && d.label === limit.latestKey) || + (d.type !== DataKeyType.function && d.name === limit.latestKey && + d.type === limit.latestKeyType); + } + + private normalizeAxisLimit(limit: any): ValueSourceConfig { + if (!limit) { + return { + type: ValueSourceType.constant, + value: null, + entityAlias: null + }; + } else if (typeof limit === 'number') { + return { + type: ValueSourceType.constant, + value: limit, + entityAlias: null + }; + } + return limit; + } } From d898674268675ef8fbd85cadae69a69ff9a6bb14 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 3 Dec 2025 17:51:25 +0200 Subject: [PATCH 3/8] clean up --- .../chart/bar-chart-basic-config.component.ts | 2 +- .../latest-chart-basic-config.component.html | 54 +++++++++---------- ...polar-area-chart-basic-config.component.ts | 2 +- .../widget/lib/chart/latest-chart.ts | 1 - .../lib/chart/time-series-chart.models.ts | 4 +- ...atest-chart-widget-settings.component.html | 14 ++--- ...ar-area-chart-widget-settings.component.ts | 27 +--------- 7 files changed, 40 insertions(+), 64 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts index 4c7da10bc6..b7d6b4f640 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts @@ -41,7 +41,7 @@ export class BarChartBasicConfigComponent extends LatestChartBasicConfigComponen barChartConfigTemplate: TemplateRef; predefinedValues = widgetTitleAutocompleteValues; - + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, protected fb: UntypedFormBuilder) { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html index d72a8d6083..c563820054 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/latest-chart-basic-config.component.html @@ -21,27 +21,27 @@ formControlName="timewindowConfig"> + [configMode]="basicMode" + hideDatasourceLabel + hideDataKeys + forceSingleDatasource + formControlName="datasources"> + panelTitle="{{ 'widgets.chart.series' | translate }}" + addKeyTitle="{{ 'widgets.chart.add-series' | translate }}" + keySettingsTitle="{{ 'widgets.chart.series-settings' | translate }}" + removeKeyTitle="{{ 'widgets.chart.remove-series' | translate }}" + noKeysText="{{ 'widgets.chart.no-series' | translate }}" + requiredKeysText="{{ 'widgets.chart.no-series-error' | translate }}" + hideUnits + hideDecimals + hideDataKeyUnits + hideDataKeyDecimals + [datasourceType]="datasource?.type" + [deviceId]="datasource?.deviceId" + [entityAliasId]="datasource?.entityAliasId" + formControlName="series">
widget-config.appearance
@@ -210,7 +210,7 @@
+ formControlName="animation">
widget-config.card-appearance
@@ -239,7 +239,7 @@
+ formControlName="actions"> @@ -346,8 +346,8 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + [series]="false">
@@ -385,9 +385,9 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + pieLabelPosition + [series]="false">
@@ -508,7 +508,7 @@
+ formControlName="fillAreaSettings">
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts index f60f8d2417..8c68ec2ff5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts @@ -41,7 +41,7 @@ export class PolarAreaChartBasicConfigComponent extends LatestChartBasicConfigCo polarAreaChartConfigTemplate: TemplateRef; predefinedValues = widgetTitleAutocompleteValues; - + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, protected fb: UntypedFormBuilder) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts index 5911a60215..9e525cd794 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts @@ -142,7 +142,6 @@ export abstract class TbLatestChart { public update(): void { for (const dsData of this.ctx.data) { - console.log("this.ctx.data",this.ctx.data) let value = 0; const tsValue = dsData.data[0]; const dataItem = this.dataItems.find(item => item.dataKey === dsData.dataKey); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 5585496ede..0ce6a4b290 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -381,8 +381,8 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting decimals?: number; interval?: number; splitNumber?: number; - min?: string | ValueSourceConfig; - max?: string | ValueSourceConfig; + min?: number | string | ValueSourceConfig; + max?: number | string | ValueSourceConfig; ticksGenerator?: TimeSeriesChartTicksGenerator | string; ticksFormatter?: TimeSeriesChartTicksFormatter | string; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html index 3f1e96ca84..66d0234914 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/latest-chart-widget-settings.component.html @@ -120,7 +120,7 @@
+ formControlName="animation">
widget-config.card-appearance
@@ -256,8 +256,8 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + [series]="false">
@@ -303,9 +303,9 @@
widgets.bar-chart.bar-appearance
+ formControlName="barSettings" + pieLabelPosition + [series]="false">
@@ -434,7 +434,7 @@
+ formControlName="fillAreaSettings">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts index 355dc55207..07d43f4de2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts @@ -26,7 +26,6 @@ import { import { LatestChartWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/latest-chart-widget-settings.component'; -import { ValueSourceConfig, ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-polar-area-chart-widget-settings', @@ -53,32 +52,10 @@ export class PolarAreaChartWidgetSettingsComponent extends LatestChartWidgetSett protected setupLatestChartControls(latestChartWidgetSettingsForm: UntypedFormGroup, settings: WidgetSettings) { latestChartWidgetSettingsForm.addControl('barSettings', this.fb.control(settings.barSettings, [])); - latestChartWidgetSettingsForm.addControl('axisMin', this.fb.control(settings.axisMin)); - latestChartWidgetSettingsForm.addControl('axisMax', this.fb.control(settings.axisMax)); + latestChartWidgetSettingsForm.addControl('axisMin', this.fb.control(settings.axisMin, [])); + latestChartWidgetSettingsForm.addControl('axisMax', this.fb.control(settings.axisMax, [])); latestChartWidgetSettingsForm.addControl('axisTickLabelFont', this.fb.control(settings.axisTickLabelFont, [])); latestChartWidgetSettingsForm.addControl('axisTickLabelColor', this.fb.control(settings.axisTickLabelColor, [Validators.min(0)])); latestChartWidgetSettingsForm.addControl('angleAxisStartAngle', this.fb.control(settings.angleAxisStartAngle, [])); } - protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { - settings.axisMin = this.normalizeAxisLimit(settings.axisMin); - settings.axisMax = this.normalizeAxisLimit(settings.axisMax); - return super.prepareInputSettings(settings); - } - - private normalizeAxisLimit(limit: any): ValueSourceConfig { - if (!limit) { - return { - type: ValueSourceType.constant, - value: null, - entityAlias: null - }; - } else if (typeof limit === 'number') { - return { - type: ValueSourceType.constant, - value: limit, - entityAlias: null - }; - } - return limit; - } } From f60a0ab915a0bad34cef4d0999c449cb163916af Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 4 Dec 2025 10:30:42 +0200 Subject: [PATCH 4/8] improved validation --- .../common/axis-scale-row.component.ts | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts index 165857eac3..9962bdeed9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts @@ -114,7 +114,6 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali } writeValue(value: ValueSourceConfig) { - console.log("value", value); this.modelValue = value; this.limitForm.patchValue( { @@ -147,14 +146,36 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali } validate(): ValidationErrors | null { - return this.limitForm.valid ? null : { - axisLimitForm: { - valid: false, - errors: this.getFormErrors() + const type = this.limitForm.get('type')?.value; + const errors: any = {}; + + if (this.limitForm.invalid) { + errors.form = this.getFormErrors(); + } + + if (type === ValueSourceType.latestKey) { + if (!this.latestKeyFormControl.value || this.latestKeyFormControl.invalid) { + errors.latestKey = { + valid: false + }; + } + } else if (type === ValueSourceType.entity) { + if (!this.limitForm.get('entityAlias')?.value) { + errors.entityAlias = { + valid: false + }; } - }; + if (!this.entityKeyFormControl.value || this.entityKeyFormControl.invalid) { + errors.entityKey = { + valid: false + }; + } + } + + return Object.keys(errors).length ? { axisLimitForm: errors } : null; } + private getFormErrors(): any { const errors: any = {}; Object.keys(this.limitForm.controls).forEach(key => { @@ -175,6 +196,7 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali this.entityKeyFormControl.clearValidators(); } else if (type === ValueSourceType.entity) { this.latestKeyFormControl.clearValidators(); + this.limitForm.get('entityAlias').setValidators([Validators.required]); this.entityKeyFormControl.setValidators([Validators.required]); } else { this.latestKeyFormControl.clearValidators(); @@ -184,6 +206,7 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali this.entityKeyFormControl.updateValueAndValidity({ emitEvent: false }); } } + private subscribeToTypeChanges() { this.limitForm.controls.type.valueChanges .pipe(takeUntilDestroyed(this.destroyRef)) From ce3b2579421e6dad5418f19c087e4e036ef7c03b Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 9 Dec 2025 12:05:11 +0200 Subject: [PATCH 5/8] clean up and propagation of limits values --- .../widget/lib/chart/time-series-chart.ts | 27 ++++---- .../common/axis-scale-row.component.scss | 15 ----- .../common/axis-scale-row.component.ts | 67 +++++-------------- ...me-series-chart-axis-settings.component.ts | 16 +---- ...ime-series-chart-y-axes-panel.component.ts | 31 +++++---- ...ime-series-chart-y-axis-row.component.html | 10 +-- .../time-series-chart-y-axis-row.component.ts | 24 ++++++- 7 files changed, 75 insertions(+), 115 deletions(-) delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index d429438f43..8568e4b13b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -594,10 +594,10 @@ export class TbTimeSeriesChart { } const yAxis = createTimeSeriesYAxis(unitSymbol, decimals, axisSettings, this.ctx.utilsService, this.darkMode, unitConvertor); if (isDefinedAndNotNull(axisSettings.min)) { - this.processYAxisLimit(axisSettings.min, 'min', yAxis, axisLimitDatasources, unitConvertor); + this.processYAxisLimit(axisSettings.min as ValueSourceConfig, 'min', yAxis, axisLimitDatasources, unitConvertor); } if (isDefinedAndNotNull(axisSettings.max)) { - this.processYAxisLimit(axisSettings.max, 'max', yAxis, axisLimitDatasources, unitConvertor); + this.processYAxisLimit(axisSettings.max as ValueSourceConfig, 'max', yAxis, axisLimitDatasources, unitConvertor); } this.yAxisList.push(yAxis); } @@ -605,22 +605,21 @@ export class TbTimeSeriesChart { } private processYAxisLimit( - limit: any, + limit: ValueSourceConfig, limitType: 'min' | 'max', yAxis: TimeSeriesChartYAxis, axisLimitDatasources: Datasource[], unitConvertor?: (value: number) => number ): void { if (limit && typeof limit === 'object' && 'type' in limit) { - const axisLimit = limit as ValueSourceConfig; - if (axisLimit.type === ValueSourceType.latestKey) { + if (limit.type === ValueSourceType.latestKey) { let latestDataKey: DataKey = null; if (this.ctx.datasources.length) { for (const datasource of this.ctx.datasources) { latestDataKey = datasource.latestDataKeys?.find(d => - (d.type === DataKeyType.function && d.label === axisLimit.latestKey) || - (d.type !== DataKeyType.function && d.name === axisLimit.latestKey && - d.type === axisLimit.latestKeyType)); + (d.type === DataKeyType.function && d.label === limit.latestKey) || + (d.type !== DataKeyType.function && d.name === limit.latestKey && + d.type === limit.latestKeyType)); if (latestDataKey) { break; } @@ -633,8 +632,8 @@ export class TbTimeSeriesChart { yAxis.maxLatestDataKey = latestDataKey; } } - } else if (axisLimit.type === ValueSourceType.entity) { - const entityAliasId = this.ctx.aliasController.getEntityAliasId(axisLimit.entityAlias); + } else if (limit.type === ValueSourceType.entity) { + const entityAliasId = this.ctx.aliasController.getEntityAliasId(limit.entityAlias); if (entityAliasId) { let datasource = axisLimitDatasources.find(d => d.entityAliasId === entityAliasId); const entityDataKey: DataKey = { @@ -651,16 +650,16 @@ export class TbTimeSeriesChart { } else { datasource = { type: DatasourceType.entity, - name: axisLimit.entityAlias, - aliasName: axisLimit.entityAlias, + name: limit.entityAlias, + aliasName: limit.entityAlias, entityAliasId, dataKeys: [entityDataKey] }; axisLimitDatasources.push(datasource); } } - } else if (axisLimit.type === ValueSourceType.constant) { - const value = unitConvertor ? unitConvertor(axisLimit.value) : axisLimit.value; + } else if (limit.type === ValueSourceType.constant) { + const value = unitConvertor ? unitConvertor(limit.value) : limit.value; if (limitType === 'min') { yAxis.option.min = value; } else { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss deleted file mode 100644 index 1a4214be18..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.scss +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright © 2016-2025 The Thingsboard Authors - * - * Licensed 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. - */ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts index 9962bdeed9..f01336d22e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.ts @@ -38,7 +38,6 @@ import { merge } from 'rxjs'; @Component({ selector: 'tb-axis-scale-row', templateUrl: './axis-scale-row.component.html', - styleUrl: './axis-scale-row.component.scss', providers: [ { provide: NG_VALUE_ACCESSOR, @@ -101,7 +100,6 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali }); this.latestKeyFormControl = this.fb.control(null, [Validators.required]); this.entityKeyFormControl = this.fb.control(null, [Validators.required]); - this.subscribeToTypeChanges(); merge( this.latestKeyFormControl.valueChanges, this.entityKeyFormControl.valueChanges, @@ -150,43 +148,25 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali const errors: any = {}; if (this.limitForm.invalid) { - errors.form = this.getFormErrors(); + errors.form = false; } if (type === ValueSourceType.latestKey) { if (!this.latestKeyFormControl.value || this.latestKeyFormControl.invalid) { - errors.latestKey = { - valid: false - }; + errors.latestKey = false; } } else if (type === ValueSourceType.entity) { if (!this.limitForm.get('entityAlias')?.value) { - errors.entityAlias = { - valid: false - }; + errors.entityAlias = false; } if (!this.entityKeyFormControl.value || this.entityKeyFormControl.invalid) { - errors.entityKey = { - valid: false - }; + errors.entityKey = false; } } return Object.keys(errors).length ? { axisLimitForm: errors } : null; } - - private getFormErrors(): any { - const errors: any = {}; - Object.keys(this.limitForm.controls).forEach(key => { - const control = this.limitForm.get(key); - if (control && control.errors) { - errors[key] = control.errors; - } - }); - return errors; - } - private updateValidators() { const axisTypeControl = this.limitForm.get('type'); if (axisTypeControl && this.entityKeyFormControl && this.latestKeyFormControl) { @@ -207,38 +187,23 @@ export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Vali } } - private subscribeToTypeChanges() { - this.limitForm.controls.type.valueChanges - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(() => { - const axisValueControl = this.limitForm.get('value'); - const axisEntityAliasControl = this.limitForm.get('entityAlias'); - if (axisValueControl) { - axisValueControl.setValue(null, { emitEvent: false }); - } - if (axisEntityAliasControl) { - axisEntityAliasControl.setValue(null, { emitEvent: false }); - } - this.latestKeyFormControl.setValue(null, { emitEvent: false }); - this.entityKeyFormControl.setValue(null, { emitEvent: false }); - - }); - } - private updateModel() { const value = this.limitForm.value; - this.modelValue.type = value.type ?? ValueSourceType.constant; - this.modelValue.value = value?.value; - this.modelValue.entityAlias = value?.entityAlias; - if (value.type === ValueSourceType.latestKey) { + const type = value.type; + let updates: Partial = { type }; + if (type === ValueSourceType.latestKey) { const latestKey: DataKey = this.latestKeyFormControl.value; - this.modelValue.latestKey = latestKey?.name; - this.modelValue.latestKeyType = (latestKey?.type as any); - } else if (value.type === ValueSourceType.entity) { + updates.latestKey = latestKey?.name; + updates.latestKeyType = latestKey?.type as any; + } else if (type === ValueSourceType.entity) { const entityKey: DataKey = this.entityKeyFormControl.value; - this.modelValue.entityKey = entityKey?.name; - this.modelValue.entityKeyType = (entityKey?.type as any); + updates.entityKey = entityKey?.name; + updates.entityKeyType = entityKey?.type as any; + updates.entityAlias = value?.entityAlias; + } else { + updates.value = value?.value; } + this.modelValue = updates as ValueSourceConfig; this.propagateChanges(this.modelValue); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts index 4bf7a78f98..9bed5d6e7c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -168,10 +168,7 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu validate(): ValidationErrors | null { return this.axisSettingsFormGroup.valid ? null : { - axisSettings: { - valid: false, - errors: this.getFormErrors() - } + axisSettings: false }; } @@ -250,17 +247,6 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu } } - private getFormErrors(): any { - const errors: any = {}; - Object.keys(this.axisSettingsFormGroup.controls).forEach(key => { - const control = this.axisSettingsFormGroup.get(key); - if (control && control.errors) { - errors[key] = control.errors; - } - }); - return errors; - } - private updateModel() { this.modelValue = this.axisSettingsFormGroup.getRawValue(); this.propagateChange(this.modelValue); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts index 3159a27abd..4d710a6ff4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts @@ -196,6 +196,8 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, const axes: TimeSeriesChartYAxisSettings[] = this.yAxesFormGroup.get('axes').value; axis.id = getNextTimeSeriesYAxisId(axes); axis.order = axes.length; + axis.min = this.normalizeAxisLimit(axis.min); + axis.max = this.normalizeAxisLimit(axis.max); const axesArray = this.yAxesFormGroup.get('axes') as UntypedFormArray; const axisControl = this.fb.control(axis, [timeSeriesChartYAxisValidator]); axesArray.push(axisControl); @@ -204,8 +206,6 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, private prepareAxesFormArray(axes: TimeSeriesChartYAxisSettings[]): UntypedFormArray { const axesControls: Array = []; axes.forEach((axis) => { - axis.min = this.normalizeAxisLimit(axis.min); - axis.max = this.normalizeAxisLimit(axis.max); axesControls.push(this.fb.control(axis, [timeSeriesChartYAxisValidator])); }); return this.fb.array(axesControls); @@ -216,17 +216,20 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, const result: TimeSeriesChartYAxes = {}; for (const [id, axis] of Object.entries(yAxes)) { + axis.min = this.normalizeAxisLimit(axis.min); + axis.max = this.normalizeAxisLimit(axis.max); + const minCfg = axis.min; + const maxCfg = axis.max; - const minCfg = axis.min as unknown as ValueSourceConfig; - const maxCfg = axis.max as unknown as ValueSourceConfig; - - const minValid = - minCfg?.type !== ValueSourceType.latestKey || - latestKeys.some(k => this.isYAxisKey(k, minCfg)); + const minValid = !!minCfg && ( + minCfg.type !== ValueSourceType.latestKey || + latestKeys.some(k => this.isYAxisKey(k, minCfg)) + ); - const maxValid = - maxCfg?.type !== ValueSourceType.latestKey || - latestKeys.some(k => this.isYAxisKey(k, maxCfg)); + const maxValid = !!maxCfg && ( + maxCfg.type !== ValueSourceType.latestKey || + latestKeys.some(k => this.isYAxisKey(k, maxCfg)) + ); if (minValid && maxValid) { result[id] = axis; @@ -288,17 +291,17 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, d.type === limit.latestKeyType); } - private normalizeAxisLimit(limit: any): ValueSourceConfig { + private normalizeAxisLimit(limit: string | number | ValueSourceConfig): ValueSourceConfig { if (!limit) { return { type: ValueSourceType.constant, value: null, entityAlias: null }; - } else if (typeof limit === 'number') { + } else if (typeof limit === 'number' || typeof limit === 'string') { return { type: ValueSourceType.constant, - value: limit, + value: Number(limit), entityAlias: null }; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html index 420e834106..6389bbd6f8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html @@ -30,14 +30,16 @@ -
+
- +
-
+
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts index 1857852afe..6f4c618394 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts @@ -43,6 +43,7 @@ import { deepClone } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { TimeSeriesChartYAxesPanelComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component'; +import { ValueSourceType } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-time-series-chart-y-axis-row', @@ -99,8 +100,8 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O position: [null, []], units: [null, []], decimals: [null, []], - min: [null, []], - max: [null, []], + min: this.createLimitFormGroup(), + max: this.createLimitFormGroup(), show: [null, []] }); this.axisFormGroup.valueChanges.pipe( @@ -195,6 +196,10 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O } } + checkIsConstantLimit(limit: 'min' | 'max') { + return this.axisFormGroup.get(`${limit}.type`)?.value === ValueSourceType.constant; + } + private updateValidators() { const show: boolean = this.axisFormGroup.get('show').value; if (show) { @@ -208,6 +213,13 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O this.axisFormGroup.get('units').disable({emitEvent: false}); this.axisFormGroup.get('decimals').disable({emitEvent: false}); } + if(!this.checkIsConstantLimit('min')){ + this.axisFormGroup.get('min').disable({emitEvent: false}); + } + if(!this.checkIsConstantLimit('max')){ + this.axisFormGroup.get('max').disable({emitEvent: false}); + } + } private updateModel() { @@ -221,4 +233,12 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O this.modelValue.show = value.show; this.propagateChange(this.modelValue); } + + private createLimitFormGroup() { + return this.fb.group({ + type: [ValueSourceType.constant, []], + value: [null, []], + entityAlias: [null, []] + }) + } } From dd7972721aa0db87ab03e2f902ec6d9e92e891c9 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 9 Dec 2025 15:31:55 +0200 Subject: [PATCH 6/8] fixed errors --- .../widget/lib/chart/time-series-chart.ts | 10 ++-- ...eries-chart-widget-settings.component.html | 3 + .../common/axis-scale-row.component.html | 2 +- ...ime-series-chart-y-axes-panel.component.ts | 2 + .../time-series-chart-y-axis-row.component.ts | 60 ++++++++++++++----- 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 8568e4b13b..9989df42e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -307,10 +307,12 @@ export class TbTimeSeriesChart { const data = this.ctx.latestData.find(d => d.dataKey === yAxis.maxLatestDataKey); if (data?.data[0]) { const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); - if (yAxis.option.max !== value) { - yAxis.option.max = value; - update = true; - } + // if (yAxis.option.max !== value) { + // yAxis.option.max = value; + // update = true; + // } + update = true; + yAxis.option.max = 124; } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index 9a80362c8d..afa3c774ec 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -62,6 +62,9 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html index 37c2838a21..0129bdc490 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/axis-scale-row.component.html @@ -35,7 +35,7 @@ formControlName="entityAlias">
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts index 4d710a6ff4..690ad6cc55 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts @@ -250,6 +250,8 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, const foundYAxisKeys: DataKey[] = []; for(const yAxis of yAxes) { + console.log("yAxis",yAxis) + const min = yAxis.min as ValueSourceConfig; const max = yAxis.max as ValueSourceConfig; if (min.type === ValueSourceType.latestKey) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts index 6f4c618394..34f1b487a2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts @@ -135,17 +135,19 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O writeValue(value: TimeSeriesChartYAxisSettings): void { this.modelValue = value; - this.axisFormGroup.patchValue( - { - label: value.label, - position: value.position, - units: value.units, - decimals: value.decimals, - min: value.min, - max: value.max, - show: value.show, - }, {emitEvent: false} - ); + const min = this.normalizeLimit(value.min); + const max = this.normalizeLimit(value.max); + + this.axisFormGroup.patchValue({ + label: value.label, + position: value.position, + units: value.units, + decimals: value.decimals, + min, + max, + show: value.show, + }, { emitEvent: false }); + this.updateValidators(); this.cd.markForCheck(); } @@ -223,7 +225,8 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O } private updateModel() { - const value = this.axisFormGroup.value; + const value = this.axisFormGroup.getRawValue(); + console.log("value", value); this.modelValue.label = value.label; this.modelValue.position = value.position; this.modelValue.units = value.units; @@ -238,7 +241,36 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O return this.fb.group({ type: [ValueSourceType.constant, []], value: [null, []], - entityAlias: [null, []] - }) + latestKey: [null, []], + latestKeyType: [null, []], + entityAlias: [null, []], + entityKey: [null, []], + entityKeyType: [null, []] + }); + } + + private normalizeLimit(limit: any) { + const base = { + type: ValueSourceType.constant, + value: null, + latestKey: null, + latestKeyType: null, + entityAlias: null, + entityKey: null, + entityKeyType: null + }; + + if (limit == null) return base; + + if (typeof limit === 'number' || typeof limit === 'string') { + return { ...base, type: ValueSourceType.constant, value: Number(limit) }; + } + + return { + ...base, + ...limit, + }; } + + } From 631a580211360669312a194f935ff1ead0c0ef1f Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 9 Dec 2025 17:40:30 +0200 Subject: [PATCH 7/8] cleanup --- .../components/widget/lib/chart/time-series-chart.ts | 10 ++++------ .../chart/time-series-chart-y-axes-panel.component.ts | 2 -- .../chart/time-series-chart-y-axis-row.component.ts | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 9989df42e8..8568e4b13b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -307,12 +307,10 @@ export class TbTimeSeriesChart { const data = this.ctx.latestData.find(d => d.dataKey === yAxis.maxLatestDataKey); if (data?.data[0]) { const value = this.parseAxisLimitData(data.data[0][1], yAxis.unitConvertor); - // if (yAxis.option.max !== value) { - // yAxis.option.max = value; - // update = true; - // } - update = true; - yAxis.option.max = 124; + if (yAxis.option.max !== value) { + yAxis.option.max = value; + update = true; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts index 690ad6cc55..4d710a6ff4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts @@ -250,8 +250,6 @@ export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, const foundYAxisKeys: DataKey[] = []; for(const yAxis of yAxes) { - console.log("yAxis",yAxis) - const min = yAxis.min as ValueSourceConfig; const max = yAxis.max as ValueSourceConfig; if (min.type === ValueSourceType.latestKey) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts index 34f1b487a2..46c7c989c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts @@ -226,7 +226,6 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O private updateModel() { const value = this.axisFormGroup.getRawValue(); - console.log("value", value); this.modelValue.label = value.label; this.modelValue.position = value.position; this.modelValue.units = value.units; From e378c2b7952f21883dc7f81c8dde0a68a77115a9 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 9 Dec 2025 17:51:56 +0200 Subject: [PATCH 8/8] enhanced validators --- .../chart/time-series-chart-y-axis-row.component.html | 6 ++---- .../common/chart/time-series-chart-y-axis-row.component.ts | 6 ++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html index 6389bbd6f8..22d3fab103 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html @@ -32,14 +32,12 @@
- +
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts index 46c7c989c0..83f1b6623a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts @@ -217,9 +217,13 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O } if(!this.checkIsConstantLimit('min')){ this.axisFormGroup.get('min').disable({emitEvent: false}); + } else { + this.axisFormGroup.get('min').enable({emitEvent: false}); } if(!this.checkIsConstantLimit('max')){ this.axisFormGroup.get('max').disable({emitEvent: false}); + } else { + this.axisFormGroup.get('max').enable({emitEvent: false}); } } @@ -270,6 +274,4 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O ...limit, }; } - - }