diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts b/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts index b50a00213b..a8a58e0578 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts @@ -20,7 +20,7 @@ import BaseGauge = CanvasGauges.BaseGauge; import { FontStyle, FontWeight } from '@home/components/widget/lib/settings.models'; import * as tinycolor_ from 'tinycolor2'; import { ColorFormats } from 'tinycolor2'; -import { isDefined, isUndefined } from '@core/utils'; +import { isDefined, isString, isUndefined } from '@core/utils'; const tinycolor = tinycolor_; @@ -32,13 +32,20 @@ export interface DigitalGaugeColorRange { rgbString: string; } +export interface colorLevelSetting { + value: number; + color: string; +} + +export type levelColors = Array; + export interface CanvasDigitalGaugeOptions extends GenericOptions { gaugeType?: GaugeType; gaugeWithScale?: number; dashThickness?: number; roundedLineCap?: boolean; gaugeColor?: string; - levelColors?: string[]; + levelColors?: levelColors; symbol?: string; label?: string; hideValue?: boolean; @@ -229,26 +236,30 @@ export class CanvasDigitalGauge extends BaseGauge { } const colorsCount = options.levelColors.length; + const isColorProperty = isString(options.levelColors[0]); const inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; options.colorsRange = []; if (options.neonGlowBrightness) { options.neonColorsRange = []; } for (let i = 0; i < options.levelColors.length; i++) { - const percentage = inc * i; - let tColor = tinycolor(options.levelColors[i]); - options.colorsRange[i] = { - pct: percentage, - color: tColor.toRgb(), - rgbString: tColor.toRgbString() - }; - if (options.neonGlowBrightness) { - tColor = tinycolor(options.levelColors[i]).brighten(options.neonGlowBrightness); - options.neonColorsRange[i] = { + let levelColor: any = options.levelColors[i]; + if (levelColor !== null) { + let percentage = isColorProperty ? inc * i : CanvasDigitalGauge.normalizeValue(levelColor.value, options.minValue, options.maxValue); + let tColor = tinycolor(isColorProperty ? levelColor : levelColor.color); + options.colorsRange[i] = { pct: percentage, color: tColor.toRgb(), rgbString: tColor.toRgbString() }; + if (options.neonGlowBrightness) { + tColor = tinycolor(isColorProperty ? levelColor : levelColor.color).brighten(options.neonGlowBrightness); + options.neonColorsRange[i] = { + pct: percentage, + color: tColor.toRgb(), + rgbString: tColor.toRgbString() + }; + } } } @@ -262,6 +273,17 @@ export class CanvasDigitalGauge extends BaseGauge { return options; } + static normalizeValue (value: number, min: number, max: number): number { + let normalValue = (value - min) / (max - min); + if (normalValue <= 0) { + return 0; + } + if (normalValue >= 1) { + return 1; + } + return normalValue; + } + private initValueClone() { const canvas = this.canvas; this.elementValueClone = canvas.element.cloneNode(true) as HTMLCanvasElementClone; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts index 51081a83c7..0af0baee96 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts @@ -19,6 +19,26 @@ import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models'; import { FontSettings } from '@home/components/widget/lib/settings.models'; +export interface colorLevelProperty { + valueSource: string; + entityAlias?: string; + attribute?: string; + value?: number; +} + +export interface fixedLevelColors { + from?: colorLevelProperty; + to?: colorLevelProperty; + color: string; +} + +export interface colorLevelSetting { + value: number; + color: string; +} + +export type colorLevel = Array; + export interface DigitalGaugeSettings { minValue?: number; maxValue?: number; @@ -38,7 +58,9 @@ export interface DigitalGaugeSettings { gaugeWidthScale?: number; defaultColor?: string; gaugeColor?: string; - levelColors?: string[]; + useFixedLevelColor?: boolean; + levelColors?: colorLevel; + fixedLevelColors?: fixedLevelColors[]; animation?: boolean; animationDuration?: number; animationRule?: AnimationRule; @@ -147,6 +169,11 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = { type: 'string', default: null }, + useFixedLevelColor: { + title: 'Use precise value for the color indicator', + type: 'boolean', + default: false + }, levelColors: { title: 'Colors of indicator, from lower to upper', type: 'array', @@ -155,6 +182,66 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = { type: 'string' } }, + fixedLevelColors: { + title: 'The colors for the indicator using boundary values', + type: 'array', + items: { + title: 'levelColor', + type: 'object', + properties: { + from: { + title: 'From', + type: 'object', + properties: { + valueSource: { + title: '[From] Value source', + type: 'string', + default: 'predefinedValue' + }, + entityAlias: { + title: '[From] Source entity alias', + type: 'string' + }, + attribute: { + title: '[From] Source entity attribute', + type: 'string' + }, + value: { + title: '[From] Value (if predefined value is selected)', + type: 'number' + } + } + }, + to: { + title: 'To', + type: 'object', + properties: { + valueSource: { + title: '[To] Value source', + type: 'string', + default: 'predefinedValue' + }, + entityAlias: { + title: '[To] Source entity alias', + type: 'string' + }, + attribute: { + title: '[To] Source entity attribute', + type: 'string' + }, + value: { + title: '[To] Value (if predefined value is selected)', + type: 'number' + } + } + }, + color: { + title: 'Color', + type: 'string' + } + } + } + }, animation: { title: 'Enable animation', type: 'boolean', @@ -343,8 +430,10 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = { key: 'gaugeColor', type: 'color' }, + 'useFixedLevelColor', { key: 'levelColors', + condition: 'model.useFixedLevelColor !== true', items: [ { key: 'levelColors[]', @@ -352,6 +441,52 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = { } ] }, + { + key: 'fixedLevelColors', + condition: 'model.useFixedLevelColor === true', + items: [ + { + key: 'fixedLevelColors[].from.valueSource', + type: 'rc-select', + multiple: false, + items: [ + { + value: 'predefinedValue', + label: 'Predefined value (Default)' + }, + { + value: 'entityAttribute', + label: 'Value taken from entity attribute' + } + ] + }, + 'fixedLevelColors[].from.value', + 'fixedLevelColors[].from.entityAlias', + 'fixedLevelColors[].from.attribute', + { + key: 'fixedLevelColors[].to.valueSource', + type: 'rc-select', + multiple: false, + items: [ + { + value: 'predefinedValue', + label: 'Predefined value (Default)' + }, + { + value: 'entityAttribute', + label: 'Value taken from entity attribute' + } + ] + }, + 'fixedLevelColors[].to.value', + 'fixedLevelColors[].to.entityAlias', + 'fixedLevelColors[].to.attribute', + { + key: 'fixedLevelColors[].color', + type: 'color' + } + ] + }, 'animation', 'animationDuration', { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts index 82f5d0e9e4..0c3dfecaa0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts @@ -16,14 +16,20 @@ import * as CanvasGauges from 'canvas-gauges'; import { WidgetContext } from '@home/models/widget-component.models'; -import { DigitalGaugeSettings, digitalGaugeSettingsSchema } from '@home/components/widget/lib/digital-gauge.models'; +import { + colorLevelSetting, + DigitalGaugeSettings, + digitalGaugeSettingsSchema +} from '@home/components/widget/lib/digital-gauge.models'; import * as tinycolor_ from 'tinycolor2'; import { isDefined } from '@core/utils'; import { prepareFontSettings } from '@home/components/widget/lib/settings.models'; import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge'; import { DatePipe } from '@angular/common'; -import { JsonSettingsSchema } from '@shared/models/widget.models'; +import {DataKey, Datasource, DatasourceType, JsonSettingsSchema, widgetType} from '@shared/models/widget.models'; import GenericOptions = CanvasGauges.GenericOptions; +import {IWidgetSubscription, WidgetSubscriptionOptions} from "@core/api/widget-api.models"; +import {DataKeyType} from "@shared/models/telemetry/telemetry.models"; const tinycolor = tinycolor_; @@ -32,6 +38,7 @@ const digitalGaugeSettingsSchemaValue = digitalGaugeSettingsSchema; export class TbCanvasDigitalGauge { private localSettings: DigitalGaugeSettings; + private levelColorsSourcesSubscription: IWidgetSubscription; private gauge: CanvasDigitalGauge; @@ -65,10 +72,16 @@ export class TbCanvasDigitalGauge { this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75; this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString(); - if (!settings.levelColors || settings.levelColors.length <= 0) { - this.localSettings.levelColors = [keyColor]; + this.localSettings.useFixedLevelColor = settings.useFixedLevelColor || false; + if (!settings.useFixedLevelColor) { + if (!settings.levelColors || settings.levelColors.length <= 0) { + this.localSettings.levelColors = [keyColor]; + } else { + this.localSettings.levelColors = settings.levelColors.slice(); + } } else { - this.localSettings.levelColors = settings.levelColors.slice(); + this.localSettings.levelColors = [keyColor]; + this.localSettings.fixedLevelColors = settings.fixedLevelColors || []; } this.localSettings.decimals = isDefined(dataKey.decimals) ? dataKey.decimals : @@ -176,6 +189,130 @@ export class TbCanvasDigitalGauge { }; this.gauge = new CanvasDigitalGauge(gaugeData).draw(); + this.init(); + } + + init() { + if (this.localSettings.useFixedLevelColor) { + if (this.localSettings.fixedLevelColors && this.localSettings.fixedLevelColors.length > 0) { + this.localSettings.levelColors = this.settingLevelColorsSubscribe(this.localSettings.fixedLevelColors); + this.updateLevelColors(this.localSettings.levelColors); + } + } + } + + settingLevelColorsSubscribe(options) { + let levelColorsDatasource: Datasource[] = []; + let predefineLevelColors: colorLevelSetting[] = []; + + function setLevelColor(levelSetting, color) { + if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) { + predefineLevelColors.push({ + value: levelSetting.value, + color: color + }) + } else if (levelSetting.entityAlias && levelSetting.attribute) { + let entityAliasId = this.ctx.aliasController.getEntityAliasId(levelSetting.entityAlias); + if (!entityAliasId) { + return; + } + + let datasource = levelColorsDatasource.find((datasource) => { + return datasource.entityAliasId === entityAliasId; + }); + + let dataKey: DataKey = { + type: DataKeyType.attribute, + name: levelSetting.attribute, + label: levelSetting.attribute, + settings: [{ + color: color, + index: predefineLevelColors.length + }], + _hash: Math.random() + }; + + if (datasource) { + let findDataKey = datasource.dataKeys.find((dataKey) => { + return dataKey.name === levelSetting.attribute; + }); + + if (findDataKey) { + findDataKey.settings.push({ + color: color, + index: predefineLevelColors.length + }); + } else { + datasource.dataKeys.push(dataKey) + } + } else { + let datasource: Datasource = { + type: DatasourceType.entity, + name: levelSetting.entityAlias, + aliasName: levelSetting.entityAlias, + entityAliasId: entityAliasId, + dataKeys: [dataKey] + }; + levelColorsDatasource.push(datasource); + } + + predefineLevelColors.push(null); + } + } + + for (let i = 0; i < options.length; i++) { + let levelColor = options[i]; + if (levelColor.from) { + setLevelColor.call(this, levelColor.from, levelColor.color); + } + if (levelColor.to) { + setLevelColor.call(this, levelColor.to, levelColor.color); + } + } + + this.subscribeLevelColorsAttributes(levelColorsDatasource); + + return predefineLevelColors; + } + + updateLevelColors(levelColors) { + (this.gauge.options as CanvasDigitalGaugeOptions).levelColors = levelColors; + this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options); + this.gauge.update({} as CanvasDigitalGaugeOptions); + } + + subscribeLevelColorsAttributes(datasources: Datasource[]) { + let TbCanvasDigitalGauge = this; + let levelColorsSourcesSubscriptionOptions: WidgetSubscriptionOptions = { + datasources: datasources, + useDashboardTimewindow: false, + type: widgetType.latest, + callbacks: { + onDataUpdated: (subscription) => { + for (let i = 0; i < subscription.data.length; i++) { + let keyData = subscription.data[i]; + if (keyData && keyData.data && keyData.data[0]) { + let attrValue = keyData.data[0][1]; + if (isFinite(attrValue)) { + for (let i = 0; i < keyData.dataKey.settings.length; i++) { + let setting = keyData.dataKey.settings[i]; + this.localSettings.levelColors[setting.index] = { + value: attrValue, + color: setting.color + }; + } + } + } + } + this.updateLevelColors(this.localSettings.levelColors); + } + } + }; + this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true).subscribe( + (subscription) => { + TbCanvasDigitalGauge.levelColorsSourcesSubscription = subscription; + } + ); } update() { diff --git a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts index 7735fe585d..d7704a2e55 100644 --- a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts +++ b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts @@ -186,7 +186,6 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato val = undefined; } if (JsonFormUtils.updateValue(key, this.model, val) || forceUpdate) { - this.formProps.model = this.model; this.isModelValid = this.validateModel(); this.updateView(); } @@ -233,7 +232,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato this.formProps.schema = this.schema; this.formProps.form = this.form; this.formProps.groupInfoes = this.groupInfoes; - this.formProps.model = deepClone(this.model); + this.formProps.model = this.model; this.renderReactSchemaForm(); }