Commit 4e16675ea43dc4b3370b1830a070ee6c259545d1

Authored by Vladyslav
Committed by GitHub
1 parent 5933397e

Add change gauge color (#2480)

... ... @@ -20,7 +20,7 @@ import BaseGauge = CanvasGauges.BaseGauge;
20 20 import { FontStyle, FontWeight } from '@home/components/widget/lib/settings.models';
21 21 import * as tinycolor_ from 'tinycolor2';
22 22 import { ColorFormats } from 'tinycolor2';
23   -import { isDefined, isUndefined } from '@core/utils';
  23 +import { isDefined, isString, isUndefined } from '@core/utils';
24 24
25 25 const tinycolor = tinycolor_;
26 26
... ... @@ -32,13 +32,20 @@ export interface DigitalGaugeColorRange {
32 32 rgbString: string;
33 33 }
34 34
  35 +export interface colorLevelSetting {
  36 + value: number;
  37 + color: string;
  38 +}
  39 +
  40 +export type levelColors = Array<string | colorLevelSetting>;
  41 +
35 42 export interface CanvasDigitalGaugeOptions extends GenericOptions {
36 43 gaugeType?: GaugeType;
37 44 gaugeWithScale?: number;
38 45 dashThickness?: number;
39 46 roundedLineCap?: boolean;
40 47 gaugeColor?: string;
41   - levelColors?: string[];
  48 + levelColors?: levelColors;
42 49 symbol?: string;
43 50 label?: string;
44 51 hideValue?: boolean;
... ... @@ -229,26 +236,30 @@ export class CanvasDigitalGauge extends BaseGauge {
229 236 }
230 237
231 238 const colorsCount = options.levelColors.length;
  239 + const isColorProperty = isString(options.levelColors[0]);
232 240 const inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
233 241 options.colorsRange = [];
234 242 if (options.neonGlowBrightness) {
235 243 options.neonColorsRange = [];
236 244 }
237 245 for (let i = 0; i < options.levelColors.length; i++) {
238   - const percentage = inc * i;
239   - let tColor = tinycolor(options.levelColors[i]);
240   - options.colorsRange[i] = {
241   - pct: percentage,
242   - color: tColor.toRgb(),
243   - rgbString: tColor.toRgbString()
244   - };
245   - if (options.neonGlowBrightness) {
246   - tColor = tinycolor(options.levelColors[i]).brighten(options.neonGlowBrightness);
247   - options.neonColorsRange[i] = {
  246 + let levelColor: any = options.levelColors[i];
  247 + if (levelColor !== null) {
  248 + let percentage = isColorProperty ? inc * i : CanvasDigitalGauge.normalizeValue(levelColor.value, options.minValue, options.maxValue);
  249 + let tColor = tinycolor(isColorProperty ? levelColor : levelColor.color);
  250 + options.colorsRange[i] = {
248 251 pct: percentage,
249 252 color: tColor.toRgb(),
250 253 rgbString: tColor.toRgbString()
251 254 };
  255 + if (options.neonGlowBrightness) {
  256 + tColor = tinycolor(isColorProperty ? levelColor : levelColor.color).brighten(options.neonGlowBrightness);
  257 + options.neonColorsRange[i] = {
  258 + pct: percentage,
  259 + color: tColor.toRgb(),
  260 + rgbString: tColor.toRgbString()
  261 + };
  262 + }
252 263 }
253 264 }
254 265
... ... @@ -262,6 +273,17 @@ export class CanvasDigitalGauge extends BaseGauge {
262 273 return options;
263 274 }
264 275
  276 + static normalizeValue (value: number, min: number, max: number): number {
  277 + let normalValue = (value - min) / (max - min);
  278 + if (normalValue <= 0) {
  279 + return 0;
  280 + }
  281 + if (normalValue >= 1) {
  282 + return 1;
  283 + }
  284 + return normalValue;
  285 + }
  286 +
265 287 private initValueClone() {
266 288 const canvas = this.canvas;
267 289 this.elementValueClone = canvas.element.cloneNode(true) as HTMLCanvasElementClone;
... ...
... ... @@ -19,6 +19,26 @@ import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge';
19 19 import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models';
20 20 import { FontSettings } from '@home/components/widget/lib/settings.models';
21 21
  22 +export interface colorLevelProperty {
  23 + valueSource: string;
  24 + entityAlias?: string;
  25 + attribute?: string;
  26 + value?: number;
  27 +}
  28 +
  29 +export interface fixedLevelColors {
  30 + from?: colorLevelProperty;
  31 + to?: colorLevelProperty;
  32 + color: string;
  33 +}
  34 +
  35 +export interface colorLevelSetting {
  36 + value: number;
  37 + color: string;
  38 +}
  39 +
  40 +export type colorLevel = Array<string | colorLevelSetting>;
  41 +
22 42 export interface DigitalGaugeSettings {
23 43 minValue?: number;
24 44 maxValue?: number;
... ... @@ -38,7 +58,9 @@ export interface DigitalGaugeSettings {
38 58 gaugeWidthScale?: number;
39 59 defaultColor?: string;
40 60 gaugeColor?: string;
41   - levelColors?: string[];
  61 + useFixedLevelColor?: boolean;
  62 + levelColors?: colorLevel;
  63 + fixedLevelColors?: fixedLevelColors[];
42 64 animation?: boolean;
43 65 animationDuration?: number;
44 66 animationRule?: AnimationRule;
... ... @@ -147,6 +169,11 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = {
147 169 type: 'string',
148 170 default: null
149 171 },
  172 + useFixedLevelColor: {
  173 + title: 'Use precise value for the color indicator',
  174 + type: 'boolean',
  175 + default: false
  176 + },
150 177 levelColors: {
151 178 title: 'Colors of indicator, from lower to upper',
152 179 type: 'array',
... ... @@ -155,6 +182,66 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = {
155 182 type: 'string'
156 183 }
157 184 },
  185 + fixedLevelColors: {
  186 + title: 'The colors for the indicator using boundary values',
  187 + type: 'array',
  188 + items: {
  189 + title: 'levelColor',
  190 + type: 'object',
  191 + properties: {
  192 + from: {
  193 + title: 'From',
  194 + type: 'object',
  195 + properties: {
  196 + valueSource: {
  197 + title: '[From] Value source',
  198 + type: 'string',
  199 + default: 'predefinedValue'
  200 + },
  201 + entityAlias: {
  202 + title: '[From] Source entity alias',
  203 + type: 'string'
  204 + },
  205 + attribute: {
  206 + title: '[From] Source entity attribute',
  207 + type: 'string'
  208 + },
  209 + value: {
  210 + title: '[From] Value (if predefined value is selected)',
  211 + type: 'number'
  212 + }
  213 + }
  214 + },
  215 + to: {
  216 + title: 'To',
  217 + type: 'object',
  218 + properties: {
  219 + valueSource: {
  220 + title: '[To] Value source',
  221 + type: 'string',
  222 + default: 'predefinedValue'
  223 + },
  224 + entityAlias: {
  225 + title: '[To] Source entity alias',
  226 + type: 'string'
  227 + },
  228 + attribute: {
  229 + title: '[To] Source entity attribute',
  230 + type: 'string'
  231 + },
  232 + value: {
  233 + title: '[To] Value (if predefined value is selected)',
  234 + type: 'number'
  235 + }
  236 + }
  237 + },
  238 + color: {
  239 + title: 'Color',
  240 + type: 'string'
  241 + }
  242 + }
  243 + }
  244 + },
158 245 animation: {
159 246 title: 'Enable animation',
160 247 type: 'boolean',
... ... @@ -343,8 +430,10 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = {
343 430 key: 'gaugeColor',
344 431 type: 'color'
345 432 },
  433 + 'useFixedLevelColor',
346 434 {
347 435 key: 'levelColors',
  436 + condition: 'model.useFixedLevelColor !== true',
348 437 items: [
349 438 {
350 439 key: 'levelColors[]',
... ... @@ -352,6 +441,52 @@ export const digitalGaugeSettingsSchema: JsonSettingsSchema = {
352 441 }
353 442 ]
354 443 },
  444 + {
  445 + key: 'fixedLevelColors',
  446 + condition: 'model.useFixedLevelColor === true',
  447 + items: [
  448 + {
  449 + key: 'fixedLevelColors[].from.valueSource',
  450 + type: 'rc-select',
  451 + multiple: false,
  452 + items: [
  453 + {
  454 + value: 'predefinedValue',
  455 + label: 'Predefined value (Default)'
  456 + },
  457 + {
  458 + value: 'entityAttribute',
  459 + label: 'Value taken from entity attribute'
  460 + }
  461 + ]
  462 + },
  463 + 'fixedLevelColors[].from.value',
  464 + 'fixedLevelColors[].from.entityAlias',
  465 + 'fixedLevelColors[].from.attribute',
  466 + {
  467 + key: 'fixedLevelColors[].to.valueSource',
  468 + type: 'rc-select',
  469 + multiple: false,
  470 + items: [
  471 + {
  472 + value: 'predefinedValue',
  473 + label: 'Predefined value (Default)'
  474 + },
  475 + {
  476 + value: 'entityAttribute',
  477 + label: 'Value taken from entity attribute'
  478 + }
  479 + ]
  480 + },
  481 + 'fixedLevelColors[].to.value',
  482 + 'fixedLevelColors[].to.entityAlias',
  483 + 'fixedLevelColors[].to.attribute',
  484 + {
  485 + key: 'fixedLevelColors[].color',
  486 + type: 'color'
  487 + }
  488 + ]
  489 + },
355 490 'animation',
356 491 'animationDuration',
357 492 {
... ...
... ... @@ -16,14 +16,20 @@
16 16
17 17 import * as CanvasGauges from 'canvas-gauges';
18 18 import { WidgetContext } from '@home/models/widget-component.models';
19   -import { DigitalGaugeSettings, digitalGaugeSettingsSchema } from '@home/components/widget/lib/digital-gauge.models';
  19 +import {
  20 + colorLevelSetting,
  21 + DigitalGaugeSettings,
  22 + digitalGaugeSettingsSchema
  23 +} from '@home/components/widget/lib/digital-gauge.models';
20 24 import * as tinycolor_ from 'tinycolor2';
21 25 import { isDefined } from '@core/utils';
22 26 import { prepareFontSettings } from '@home/components/widget/lib/settings.models';
23 27 import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
24 28 import { DatePipe } from '@angular/common';
25   -import { JsonSettingsSchema } from '@shared/models/widget.models';
  29 +import {DataKey, Datasource, DatasourceType, JsonSettingsSchema, widgetType} from '@shared/models/widget.models';
26 30 import GenericOptions = CanvasGauges.GenericOptions;
  31 +import {IWidgetSubscription, WidgetSubscriptionOptions} from "@core/api/widget-api.models";
  32 +import {DataKeyType} from "@shared/models/telemetry/telemetry.models";
27 33
28 34 const tinycolor = tinycolor_;
29 35
... ... @@ -32,6 +38,7 @@ const digitalGaugeSettingsSchemaValue = digitalGaugeSettingsSchema;
32 38 export class TbCanvasDigitalGauge {
33 39
34 40 private localSettings: DigitalGaugeSettings;
  41 + private levelColorsSourcesSubscription: IWidgetSubscription;
35 42
36 43 private gauge: CanvasDigitalGauge;
37 44
... ... @@ -65,10 +72,16 @@ export class TbCanvasDigitalGauge {
65 72 this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75;
66 73 this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString();
67 74
68   - if (!settings.levelColors || settings.levelColors.length <= 0) {
69   - this.localSettings.levelColors = [keyColor];
  75 + this.localSettings.useFixedLevelColor = settings.useFixedLevelColor || false;
  76 + if (!settings.useFixedLevelColor) {
  77 + if (!settings.levelColors || settings.levelColors.length <= 0) {
  78 + this.localSettings.levelColors = [keyColor];
  79 + } else {
  80 + this.localSettings.levelColors = settings.levelColors.slice();
  81 + }
70 82 } else {
71   - this.localSettings.levelColors = settings.levelColors.slice();
  83 + this.localSettings.levelColors = [keyColor];
  84 + this.localSettings.fixedLevelColors = settings.fixedLevelColors || [];
72 85 }
73 86
74 87 this.localSettings.decimals = isDefined(dataKey.decimals) ? dataKey.decimals :
... ... @@ -176,6 +189,130 @@ export class TbCanvasDigitalGauge {
176 189 };
177 190
178 191 this.gauge = new CanvasDigitalGauge(gaugeData).draw();
  192 + this.init();
  193 + }
  194 +
  195 + init() {
  196 + if (this.localSettings.useFixedLevelColor) {
  197 + if (this.localSettings.fixedLevelColors && this.localSettings.fixedLevelColors.length > 0) {
  198 + this.localSettings.levelColors = this.settingLevelColorsSubscribe(this.localSettings.fixedLevelColors);
  199 + this.updateLevelColors(this.localSettings.levelColors);
  200 + }
  201 + }
  202 + }
  203 +
  204 + settingLevelColorsSubscribe(options) {
  205 + let levelColorsDatasource: Datasource[] = [];
  206 + let predefineLevelColors: colorLevelSetting[] = [];
  207 +
  208 + function setLevelColor(levelSetting, color) {
  209 + if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) {
  210 + predefineLevelColors.push({
  211 + value: levelSetting.value,
  212 + color: color
  213 + })
  214 + } else if (levelSetting.entityAlias && levelSetting.attribute) {
  215 + let entityAliasId = this.ctx.aliasController.getEntityAliasId(levelSetting.entityAlias);
  216 + if (!entityAliasId) {
  217 + return;
  218 + }
  219 +
  220 + let datasource = levelColorsDatasource.find((datasource) => {
  221 + return datasource.entityAliasId === entityAliasId;
  222 + });
  223 +
  224 + let dataKey: DataKey = {
  225 + type: DataKeyType.attribute,
  226 + name: levelSetting.attribute,
  227 + label: levelSetting.attribute,
  228 + settings: [{
  229 + color: color,
  230 + index: predefineLevelColors.length
  231 + }],
  232 + _hash: Math.random()
  233 + };
  234 +
  235 + if (datasource) {
  236 + let findDataKey = datasource.dataKeys.find((dataKey) => {
  237 + return dataKey.name === levelSetting.attribute;
  238 + });
  239 +
  240 + if (findDataKey) {
  241 + findDataKey.settings.push({
  242 + color: color,
  243 + index: predefineLevelColors.length
  244 + });
  245 + } else {
  246 + datasource.dataKeys.push(dataKey)
  247 + }
  248 + } else {
  249 + let datasource: Datasource = {
  250 + type: DatasourceType.entity,
  251 + name: levelSetting.entityAlias,
  252 + aliasName: levelSetting.entityAlias,
  253 + entityAliasId: entityAliasId,
  254 + dataKeys: [dataKey]
  255 + };
  256 + levelColorsDatasource.push(datasource);
  257 + }
  258 +
  259 + predefineLevelColors.push(null);
  260 + }
  261 + }
  262 +
  263 + for (let i = 0; i < options.length; i++) {
  264 + let levelColor = options[i];
  265 + if (levelColor.from) {
  266 + setLevelColor.call(this, levelColor.from, levelColor.color);
  267 + }
  268 + if (levelColor.to) {
  269 + setLevelColor.call(this, levelColor.to, levelColor.color);
  270 + }
  271 + }
  272 +
  273 + this.subscribeLevelColorsAttributes(levelColorsDatasource);
  274 +
  275 + return predefineLevelColors;
  276 + }
  277 +
  278 + updateLevelColors(levelColors) {
  279 + (this.gauge.options as CanvasDigitalGaugeOptions).levelColors = levelColors;
  280 + this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options);
  281 + this.gauge.update({} as CanvasDigitalGaugeOptions);
  282 + }
  283 +
  284 + subscribeLevelColorsAttributes(datasources: Datasource[]) {
  285 + let TbCanvasDigitalGauge = this;
  286 + let levelColorsSourcesSubscriptionOptions: WidgetSubscriptionOptions = {
  287 + datasources: datasources,
  288 + useDashboardTimewindow: false,
  289 + type: widgetType.latest,
  290 + callbacks: {
  291 + onDataUpdated: (subscription) => {
  292 + for (let i = 0; i < subscription.data.length; i++) {
  293 + let keyData = subscription.data[i];
  294 + if (keyData && keyData.data && keyData.data[0]) {
  295 + let attrValue = keyData.data[0][1];
  296 + if (isFinite(attrValue)) {
  297 + for (let i = 0; i < keyData.dataKey.settings.length; i++) {
  298 + let setting = keyData.dataKey.settings[i];
  299 + this.localSettings.levelColors[setting.index] = {
  300 + value: attrValue,
  301 + color: setting.color
  302 + };
  303 + }
  304 + }
  305 + }
  306 + }
  307 + this.updateLevelColors(this.localSettings.levelColors);
  308 + }
  309 + }
  310 + };
  311 + this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true).subscribe(
  312 + (subscription) => {
  313 + TbCanvasDigitalGauge.levelColorsSourcesSubscription = subscription;
  314 + }
  315 + );
179 316 }
180 317
181 318 update() {
... ...
... ... @@ -186,7 +186,6 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
186 186 val = undefined;
187 187 }
188 188 if (JsonFormUtils.updateValue(key, this.model, val) || forceUpdate) {
189   - this.formProps.model = this.model;
190 189 this.isModelValid = this.validateModel();
191 190 this.updateView();
192 191 }
... ... @@ -233,7 +232,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
233 232 this.formProps.schema = this.schema;
234 233 this.formProps.form = this.form;
235 234 this.formProps.groupInfoes = this.groupInfoes;
236   - this.formProps.model = deepClone(this.model);
  235 + this.formProps.model = this.model;
237 236 this.renderReactSchemaForm();
238 237 }
239 238
... ...