Commit 4e16675ea43dc4b3370b1830a070ee6c259545d1
Committed by
GitHub
1 parent
5933397e
Add change gauge color (#2480)
Showing
4 changed files
with
313 additions
and
20 deletions
... | ... | @@ -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 | ... | ... |