Showing
18 changed files
with
1996 additions
and
64 deletions
@@ -43,7 +43,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid | @@ -43,7 +43,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid | ||
43 | constructor(@Inject(RafService) public raf: RafService, | 43 | constructor(@Inject(RafService) public raf: RafService, |
44 | @Inject(Store) protected store: Store<AppState>, | 44 | @Inject(Store) protected store: Store<AppState>, |
45 | @Inject(FormBuilder) public fb: FormBuilder, | 45 | @Inject(FormBuilder) public fb: FormBuilder, |
46 | - @Inject(Injector) private $injector: Injector, | 46 | + @Inject(Injector) public readonly $injector: Injector, |
47 | @Inject('widgetContext') public readonly ctx: WidgetContext, | 47 | @Inject('widgetContext') public readonly ctx: WidgetContext, |
48 | @Inject('errorMessages') public readonly errorMessages: string[]) { | 48 | @Inject('errorMessages') public readonly errorMessages: string[]) { |
49 | super(store); | 49 | super(store); |
@@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | - | 17 | +import * as CanvasGauges from 'canvas-gauges'; |
18 | import { FontSettings, getFontFamily } from '@home/components/widget/lib/settings.models'; | 18 | import { FontSettings, getFontFamily } from '@home/components/widget/lib/settings.models'; |
19 | import { JsonSettingsSchema } from '@shared/models/widget.models'; | 19 | import { JsonSettingsSchema } from '@shared/models/widget.models'; |
20 | import { WidgetContext } from '@home/models/widget-component.models'; | 20 | import { WidgetContext } from '@home/models/widget-component.models'; |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import * as CanvasGauges from 'canvas-gauges'; | ||
18 | +import GenericOptions = CanvasGauges.GenericOptions; | ||
19 | +import BaseGauge = CanvasGauges.BaseGauge; | ||
20 | +import { FontStyle, FontWeight } from '@home/components/widget/lib/settings.models'; | ||
21 | +import * as tinycolor_ from 'tinycolor2'; | ||
22 | +import { ColorFormats } from 'tinycolor2'; | ||
23 | +import { isDefined, isUndefined } from '@core/utils'; | ||
24 | + | ||
25 | +const tinycolor = tinycolor_; | ||
26 | + | ||
27 | +export type GaugeType = 'arc' | 'donut' | 'horizontalBar' | 'verticalBar'; | ||
28 | + | ||
29 | +export interface DigitalGaugeColorRange { | ||
30 | + pct: number; | ||
31 | + color: ColorFormats.RGBA; | ||
32 | + rgbString: string; | ||
33 | +} | ||
34 | + | ||
35 | +export interface CanvasDigitalGaugeOptions extends GenericOptions { | ||
36 | + gaugeType?: GaugeType; | ||
37 | + gaugeWithScale?: number; | ||
38 | + dashThickness?: number; | ||
39 | + roundedLineCap?: boolean; | ||
40 | + gaugeColor?: string; | ||
41 | + levelColors?: string[]; | ||
42 | + symbol?: string; | ||
43 | + label?: string; | ||
44 | + hideValue?: boolean; | ||
45 | + hideMinMax?: boolean; | ||
46 | + fontTitle?: string; | ||
47 | + fontValue?: string; | ||
48 | + fontMinMaxSize?: number; | ||
49 | + fontMinMaxStyle?: FontStyle; | ||
50 | + fontMinMaxWeight?: FontWeight; | ||
51 | + colorMinMax?: string; | ||
52 | + fontMinMax?: string; | ||
53 | + fontLabelSize?: number; | ||
54 | + fontLabelStyle?: FontStyle; | ||
55 | + fontLabelWeight?: FontWeight; | ||
56 | + colorLabel?: string; | ||
57 | + colorValue?: string; | ||
58 | + fontLabel?: string; | ||
59 | + neonGlowBrightness?: number; | ||
60 | + isMobile?: boolean; | ||
61 | + donutStartAngle?: number; | ||
62 | + donutEndAngle?: number; | ||
63 | + | ||
64 | + colorsRange?: DigitalGaugeColorRange[]; | ||
65 | + neonColorsRange?: DigitalGaugeColorRange[]; | ||
66 | + neonColorTitle?: string; | ||
67 | + neonColorLabel?: string; | ||
68 | + neonColorValue?: string; | ||
69 | + neonColorMinMax?: string; | ||
70 | + timestamp?: number; | ||
71 | + gaugeWidthScale?: number; | ||
72 | + fontTitleHeight?: FontHeightInfo; | ||
73 | + fontLabelHeight?: FontHeightInfo; | ||
74 | + fontValueHeight?: FontHeightInfo; | ||
75 | + fontMinMaxHeight?: FontHeightInfo; | ||
76 | + | ||
77 | + showTimestamp?: boolean; | ||
78 | +} | ||
79 | + | ||
80 | +const defaultDigitalGaugeOptions: CanvasDigitalGaugeOptions = { ...GenericOptions, | ||
81 | + ...{ | ||
82 | + gaugeType: 'arc', | ||
83 | + gaugeWithScale: 0.75, | ||
84 | + dashThickness: 0, | ||
85 | + roundedLineCap: false, | ||
86 | + | ||
87 | + gaugeColor: '#777', | ||
88 | + levelColors: ['blue'], | ||
89 | + | ||
90 | + symbol: '', | ||
91 | + label: '', | ||
92 | + hideValue: false, | ||
93 | + hideMinMax: false, | ||
94 | + | ||
95 | + fontTitle: 'Roboto', | ||
96 | + | ||
97 | + fontValue: 'Roboto', | ||
98 | + | ||
99 | + fontMinMaxSize: 10, | ||
100 | + fontMinMaxStyle: 'normal', | ||
101 | + fontMinMaxWeight: '500', | ||
102 | + colorMinMax: '#eee', | ||
103 | + fontMinMax: 'Roboto', | ||
104 | + | ||
105 | + fontLabelSize: 8, | ||
106 | + fontLabelStyle: 'normal', | ||
107 | + fontLabelWeight: '500', | ||
108 | + colorLabel: '#eee', | ||
109 | + fontLabel: 'Roboto', | ||
110 | + | ||
111 | + neonGlowBrightness: 0, | ||
112 | + | ||
113 | + isMobile: false | ||
114 | + } | ||
115 | +}; | ||
116 | + | ||
117 | +BaseGauge.initialize('CanvasDigitalGauge', defaultDigitalGaugeOptions); | ||
118 | + | ||
119 | +interface HTMLCanvasElementClone extends HTMLCanvasElement { | ||
120 | + initialized?: boolean; | ||
121 | + renderedTimestamp?: number; | ||
122 | + renderedValue?: number; | ||
123 | + renderedProgress?: string; | ||
124 | +} | ||
125 | + | ||
126 | +interface DigitalGaugeCanvasRenderingContext2D extends CanvasRenderingContext2D { | ||
127 | + barDimensions?: BarDimensions; | ||
128 | + currentColor?: string; | ||
129 | +} | ||
130 | + | ||
131 | +interface BarDimensions { | ||
132 | + baseX: number; | ||
133 | + baseY: number; | ||
134 | + width: number; | ||
135 | + height: number; | ||
136 | + origBaseX?: number; | ||
137 | + origBaseY?: number; | ||
138 | + fontSizeFactor?: number; | ||
139 | + Ro?: number; | ||
140 | + Cy?: number; | ||
141 | + titleY?: number; | ||
142 | + titleBottom?: number; | ||
143 | + Ri?: number; | ||
144 | + Cx?: number; | ||
145 | + strokeWidth?: number; | ||
146 | + Rm?: number; | ||
147 | + fontValueBaseline?: CanvasTextBaseline; | ||
148 | + fontMinMaxBaseline?: CanvasTextBaseline; | ||
149 | + fontMinMaxAlign?: CanvasTextAlign; | ||
150 | + labelY?: number; | ||
151 | + valueY?: number; | ||
152 | + minY?: number; | ||
153 | + maxY?: number; | ||
154 | + minX?: number; | ||
155 | + maxX?: number; | ||
156 | + barTop?: number; | ||
157 | + barBottom?: number; | ||
158 | + barLeft?: number; | ||
159 | + barRight?: number; | ||
160 | + dashLength?: number; | ||
161 | +} | ||
162 | + | ||
163 | +interface FontHeightInfo { | ||
164 | + ascent?: number; | ||
165 | + height?: number; | ||
166 | + descent?: number; | ||
167 | +} | ||
168 | + | ||
169 | +export class CanvasDigitalGauge extends BaseGauge { | ||
170 | + | ||
171 | + static heightCache: {[key: string]: FontHeightInfo} = {}; | ||
172 | + | ||
173 | + private elementValueClone: HTMLCanvasElementClone; | ||
174 | + private contextValueClone: DigitalGaugeCanvasRenderingContext2D; | ||
175 | + | ||
176 | + private elementProgressClone: HTMLCanvasElementClone; | ||
177 | + private contextProgressClone: DigitalGaugeCanvasRenderingContext2D; | ||
178 | + | ||
179 | + public _value: number; | ||
180 | + | ||
181 | + constructor(options: CanvasDigitalGaugeOptions) { | ||
182 | + options = {...defaultDigitalGaugeOptions,...(options || {})}; | ||
183 | + super(CanvasDigitalGauge.configure(options)); | ||
184 | + this.initValueClone(); | ||
185 | + } | ||
186 | + | ||
187 | + static configure(options: CanvasDigitalGaugeOptions): CanvasDigitalGaugeOptions { | ||
188 | + | ||
189 | + if (options.value > options.maxValue) { | ||
190 | + options.value = options.maxValue; | ||
191 | + } | ||
192 | + | ||
193 | + if (options.value < options.minValue) { | ||
194 | + options.value = options.minValue; | ||
195 | + } | ||
196 | + | ||
197 | + if (options.gaugeType === 'donut') { | ||
198 | + if (!options.donutStartAngle) { | ||
199 | + options.donutStartAngle = 1.5 * Math.PI; | ||
200 | + } | ||
201 | + if (!options.donutEndAngle) { | ||
202 | + options.donutEndAngle = options.donutStartAngle + 2 * Math.PI; | ||
203 | + } | ||
204 | + } | ||
205 | + | ||
206 | + const colorsCount = options.levelColors.length; | ||
207 | + const inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; | ||
208 | + options.colorsRange = []; | ||
209 | + if (options.neonGlowBrightness) { | ||
210 | + options.neonColorsRange = []; | ||
211 | + } | ||
212 | + for (let i = 0; i < options.levelColors.length; i++) { | ||
213 | + const percentage = inc * i; | ||
214 | + let tColor = tinycolor(options.levelColors[i]); | ||
215 | + options.colorsRange[i] = { | ||
216 | + pct: percentage, | ||
217 | + color: tColor.toRgb(), | ||
218 | + rgbString: tColor.toRgbString() | ||
219 | + }; | ||
220 | + if (options.neonGlowBrightness) { | ||
221 | + tColor = tinycolor(options.levelColors[i]).brighten(options.neonGlowBrightness); | ||
222 | + options.neonColorsRange[i] = { | ||
223 | + pct: percentage, | ||
224 | + color: tColor.toRgb(), | ||
225 | + rgbString: tColor.toRgbString() | ||
226 | + }; | ||
227 | + } | ||
228 | + } | ||
229 | + | ||
230 | + if (options.neonGlowBrightness) { | ||
231 | + options.neonColorTitle = tinycolor(options.colorTitle).brighten(options.neonGlowBrightness).toHexString(); | ||
232 | + options.neonColorLabel = tinycolor(options.colorLabel).brighten(options.neonGlowBrightness).toHexString(); | ||
233 | + options.neonColorValue = tinycolor(options.colorValue).brighten(options.neonGlowBrightness).toHexString(); | ||
234 | + options.neonColorMinMax = tinycolor(options.colorMinMax).brighten(options.neonGlowBrightness).toHexString(); | ||
235 | + } | ||
236 | + | ||
237 | + return options; | ||
238 | + } | ||
239 | + | ||
240 | + private initValueClone() { | ||
241 | + const canvas = this.canvas; | ||
242 | + this.elementValueClone = canvas.element.cloneNode(true) as HTMLCanvasElementClone; | ||
243 | + this.contextValueClone = this.elementValueClone.getContext('2d'); | ||
244 | + this.elementValueClone.initialized = false; | ||
245 | + | ||
246 | + this.contextValueClone.translate(canvas.drawX, canvas.drawY); | ||
247 | + this.contextValueClone.save(); | ||
248 | + | ||
249 | + this.elementProgressClone = canvas.element.cloneNode(true) as HTMLCanvasElementClone; | ||
250 | + this.contextProgressClone = this.elementProgressClone.getContext('2d'); | ||
251 | + this.elementProgressClone.initialized = false; | ||
252 | + | ||
253 | + this.contextProgressClone.translate(canvas.drawX, canvas.drawY); | ||
254 | + this.contextProgressClone.save(); | ||
255 | + } | ||
256 | + | ||
257 | + destroy() { | ||
258 | + this.contextValueClone = null; | ||
259 | + this.elementValueClone = null; | ||
260 | + this.contextProgressClone = null; | ||
261 | + this.elementProgressClone = null; | ||
262 | + super.destroy(); | ||
263 | + } | ||
264 | + | ||
265 | + update(options: GenericOptions): BaseGauge { | ||
266 | + this.canvas.onRedraw = null; | ||
267 | + const result = super.update(options); | ||
268 | + this.initValueClone(); | ||
269 | + this.canvas.onRedraw = this.draw.bind(this); | ||
270 | + this.draw(); | ||
271 | + return result; | ||
272 | + } | ||
273 | + | ||
274 | + set timestamp(timestamp: number) { | ||
275 | + (this.options as CanvasDigitalGaugeOptions).timestamp = timestamp; | ||
276 | + this.draw(); | ||
277 | + } | ||
278 | + | ||
279 | + get timestamp(): number { | ||
280 | + return (this.options as CanvasDigitalGaugeOptions).timestamp; | ||
281 | + } | ||
282 | + | ||
283 | + draw(): CanvasDigitalGauge { | ||
284 | + try { | ||
285 | + const canvas = this.canvas; | ||
286 | + if (!canvas.drawWidth || !canvas.drawHeight) { | ||
287 | + return this; | ||
288 | + } | ||
289 | + const [x, y, w, h] = [ | ||
290 | + -canvas.drawX, | ||
291 | + -canvas.drawY, | ||
292 | + canvas.drawWidth, | ||
293 | + canvas.drawHeight | ||
294 | + ]; | ||
295 | + const options = this.options as CanvasDigitalGaugeOptions; | ||
296 | + const elementClone = canvas.elementClone as HTMLCanvasElementClone; | ||
297 | + if (!elementClone.initialized) { | ||
298 | + const context = canvas.contextClone; | ||
299 | + | ||
300 | + // clear the cache | ||
301 | + context.clearRect(x, y, w, h); | ||
302 | + context.save(); | ||
303 | + | ||
304 | + const canvasContext = canvas.context as DigitalGaugeCanvasRenderingContext2D; | ||
305 | + | ||
306 | + canvasContext.barDimensions = barDimensions(context, options, x, y, w, h); | ||
307 | + this.contextValueClone.barDimensions = canvasContext.barDimensions; | ||
308 | + this.contextProgressClone.barDimensions = canvasContext.barDimensions; | ||
309 | + | ||
310 | + drawBackground(context, options); | ||
311 | + | ||
312 | + drawDigitalTitle(context, options); | ||
313 | + | ||
314 | + if (!options.showTimestamp) { | ||
315 | + drawDigitalLabel(context, options); | ||
316 | + } | ||
317 | + | ||
318 | + drawDigitalMinMax(context, options); | ||
319 | + | ||
320 | + elementClone.initialized = true; | ||
321 | + } | ||
322 | + | ||
323 | + let valueChanged = false; | ||
324 | + if (!this.elementValueClone.initialized || | ||
325 | + isDefined(this._value) && this.elementValueClone.renderedValue !== this._value || | ||
326 | + (options.showTimestamp && this.elementValueClone.renderedTimestamp !== this.timestamp)) { | ||
327 | + if (isDefined(this._value)) { | ||
328 | + this.elementValueClone.renderedValue = this._value; | ||
329 | + } | ||
330 | + if (isUndefined(this.elementValueClone.renderedValue)) { | ||
331 | + this.elementValueClone.renderedValue = this.value; | ||
332 | + } | ||
333 | + const context = this.contextValueClone; | ||
334 | + // clear the cache | ||
335 | + context.clearRect(x, y, w, h); | ||
336 | + context.save(); | ||
337 | + | ||
338 | + context.drawImage(canvas.elementClone, x, y, w, h); | ||
339 | + context.save(); | ||
340 | + | ||
341 | + drawDigitalValue(context, options, this.elementValueClone.renderedValue); | ||
342 | + | ||
343 | + if (options.showTimestamp) { | ||
344 | + drawDigitalLabel(context, options); | ||
345 | + this.elementValueClone.renderedTimestamp = this.timestamp; | ||
346 | + } | ||
347 | + | ||
348 | + this.elementValueClone.initialized = true; | ||
349 | + | ||
350 | + valueChanged = true; | ||
351 | + } | ||
352 | + | ||
353 | + const progress = (CanvasGauges.drawings.normalizedValue(options).normal - options.minValue) / | ||
354 | + (options.maxValue - options.minValue); | ||
355 | + | ||
356 | + const fixedProgress = progress.toFixed(3); | ||
357 | + | ||
358 | + if (!this.elementProgressClone.initialized || this.elementProgressClone.renderedProgress !== fixedProgress || valueChanged) { | ||
359 | + const context = this.contextProgressClone; | ||
360 | + // clear the cache | ||
361 | + context.clearRect(x, y, w, h); | ||
362 | + context.save(); | ||
363 | + | ||
364 | + context.drawImage(this.elementValueClone, x, y, w, h); | ||
365 | + context.save(); | ||
366 | + | ||
367 | + if (Number(fixedProgress) > 0) { | ||
368 | + drawProgress(context, options, progress); | ||
369 | + } | ||
370 | + | ||
371 | + this.elementProgressClone.initialized = true; | ||
372 | + this.elementProgressClone.renderedProgress = fixedProgress; | ||
373 | + } | ||
374 | + | ||
375 | + this.canvas.commit(); | ||
376 | + | ||
377 | + // clear the canvas | ||
378 | + canvas.context.clearRect(x, y, w, h); | ||
379 | + canvas.context.save(); | ||
380 | + | ||
381 | + canvas.context.drawImage(this.elementProgressClone, x, y, w, h); | ||
382 | + canvas.context.save(); | ||
383 | + | ||
384 | + // @ts-ignore | ||
385 | + super.draw(); | ||
386 | + | ||
387 | + } catch (err) { | ||
388 | + CanvasGauges.drawings.verifyError(err); | ||
389 | + } | ||
390 | + return this; | ||
391 | + } | ||
392 | + | ||
393 | + getValueColor() { | ||
394 | + if (this.contextProgressClone) { | ||
395 | + let color = this.contextProgressClone.currentColor; | ||
396 | + const options = this.options as CanvasDigitalGaugeOptions; | ||
397 | + if (!color) { | ||
398 | + if (options.neonGlowBrightness) { | ||
399 | + color = getProgressColor(0, options.neonColorsRange); | ||
400 | + } else { | ||
401 | + color = getProgressColor(0, options.colorsRange); | ||
402 | + } | ||
403 | + } | ||
404 | + return color; | ||
405 | + } else { | ||
406 | + return '#000'; | ||
407 | + } | ||
408 | + } | ||
409 | +} | ||
410 | + | ||
411 | +function barDimensions(context: DigitalGaugeCanvasRenderingContext2D, | ||
412 | + options: CanvasDigitalGaugeOptions, | ||
413 | + x: number, y: number, w: number, h: number): BarDimensions { | ||
414 | + context.barDimensions = { | ||
415 | + baseX: x, | ||
416 | + baseY: y, | ||
417 | + width: w, | ||
418 | + height: h | ||
419 | + }; | ||
420 | + | ||
421 | + const bd = context.barDimensions; | ||
422 | + | ||
423 | + let aspect = 1; | ||
424 | + | ||
425 | + if (options.gaugeType === 'horizontalBar') { | ||
426 | + aspect = options.title === '' ? 2.5 : 2; | ||
427 | + } else if (options.gaugeType === 'verticalBar') { | ||
428 | + aspect = options.hideMinMax ? 0.35 : 0.5; | ||
429 | + } else if (options.gaugeType === 'arc') { | ||
430 | + aspect = 1.5; | ||
431 | + } | ||
432 | + | ||
433 | + const currentAspect = w / h; | ||
434 | + if (currentAspect > aspect) { | ||
435 | + bd.width = (h * aspect); | ||
436 | + bd.height = h; | ||
437 | + } else { | ||
438 | + bd.width = w; | ||
439 | + bd.height = w / aspect; | ||
440 | + } | ||
441 | + | ||
442 | + bd.origBaseX = bd.baseX; | ||
443 | + bd.origBaseY = bd.baseY; | ||
444 | + bd.baseX += (w - bd.width) / 2; | ||
445 | + bd.baseY += (h - bd.height) / 2; | ||
446 | + | ||
447 | + if (options.gaugeType === 'donut') { | ||
448 | + bd.fontSizeFactor = Math.max(bd.width, bd.height) / 125; | ||
449 | + } else if (options.gaugeType === 'verticalBar' || (options.gaugeType === 'arc' && options.title === '')) { | ||
450 | + bd.fontSizeFactor = Math.max(bd.width, bd.height) / 150; | ||
451 | + } else { | ||
452 | + bd.fontSizeFactor = Math.max(bd.width, bd.height) / 200; | ||
453 | + } | ||
454 | + | ||
455 | + const gws = options.gaugeWidthScale; | ||
456 | + | ||
457 | + if (options.neonGlowBrightness) { | ||
458 | + options.fontTitleHeight = determineFontHeight(options, 'Title', bd.fontSizeFactor); | ||
459 | + options.fontLabelHeight = determineFontHeight(options, 'Label', bd.fontSizeFactor); | ||
460 | + options.fontValueHeight = determineFontHeight(options, 'Value', bd.fontSizeFactor); | ||
461 | + options.fontMinMaxHeight = determineFontHeight(options, 'MinMax', bd.fontSizeFactor); | ||
462 | + } | ||
463 | + | ||
464 | + if (options.gaugeType === 'donut') { | ||
465 | + bd.Ro = bd.width / 2 - bd.width / 20; | ||
466 | + bd.Cy = bd.baseY + bd.height / 2; | ||
467 | + if (options.title && typeof options.title === 'string' && options.title.length > 0) { | ||
468 | + let titleOffset = determineFontHeight(options, 'Title', bd.fontSizeFactor).height; | ||
469 | + titleOffset += bd.fontSizeFactor * 2; | ||
470 | + bd.titleY = bd.baseY + titleOffset; | ||
471 | + titleOffset += bd.fontSizeFactor * 2; | ||
472 | + bd.Cy += titleOffset/2; | ||
473 | + bd.Ro -= titleOffset/2; | ||
474 | + } | ||
475 | + bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws * 1.2; | ||
476 | + bd.Cx = bd.baseX + bd.width / 2; | ||
477 | + } else if (options.gaugeType === 'arc') { | ||
478 | + if (options.title && typeof options.title === 'string' && options.title.length > 0) { | ||
479 | + bd.Ro = bd.width / 2 - bd.width / 7; | ||
480 | + bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws; | ||
481 | + } else { | ||
482 | + bd.Ro = bd.width / 2 - bd.fontSizeFactor * 4; | ||
483 | + bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws * 1.2; | ||
484 | + } | ||
485 | + bd.Cx = bd.baseX + bd.width / 2; | ||
486 | + bd.Cy = bd.baseY + bd.height / 1.25; | ||
487 | + } else if (options.gaugeType === 'verticalBar') { | ||
488 | + bd.Ro = bd.width / 2 - bd.width / 10; | ||
489 | + bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws * (options.hideMinMax ? 4 : 2.5); | ||
490 | + } else { // horizontalBar | ||
491 | + bd.Ro = bd.width / 2 - bd.width / 10; | ||
492 | + bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws; | ||
493 | + } | ||
494 | + | ||
495 | + bd.strokeWidth = bd.Ro - bd.Ri; | ||
496 | + bd.Rm = bd.Ri + bd.strokeWidth * 0.5; | ||
497 | + | ||
498 | + bd.fontValueBaseline = 'alphabetic'; | ||
499 | + bd.fontMinMaxBaseline = 'alphabetic'; | ||
500 | + bd.fontMinMaxAlign = 'center'; | ||
501 | + | ||
502 | + if (options.gaugeType === 'donut') { | ||
503 | + bd.fontValueBaseline = 'middle'; | ||
504 | + if (options.label && options.label.length > 0) { | ||
505 | + const valueHeight = determineFontHeight(options, 'Value', bd.fontSizeFactor).height; | ||
506 | + const labelHeight = determineFontHeight(options, 'Label', bd.fontSizeFactor).height; | ||
507 | + const total = valueHeight + labelHeight; | ||
508 | + bd.labelY = bd.Cy + total/2; | ||
509 | + bd.valueY = bd.Cy - total/2 + valueHeight/2; | ||
510 | + } else { | ||
511 | + bd.valueY = bd.Cy; | ||
512 | + } | ||
513 | + } else if (options.gaugeType === 'arc') { | ||
514 | + bd.titleY = bd.Cy - bd.Ro - 12 * bd.fontSizeFactor; | ||
515 | + bd.valueY = bd.Cy; | ||
516 | + bd.labelY = bd.Cy + (8 + options.fontLabelSize) * bd.fontSizeFactor; | ||
517 | + bd.minY = bd.maxY = bd.labelY; | ||
518 | + if (options.roundedLineCap) { | ||
519 | + bd.minY += bd.strokeWidth/2; | ||
520 | + bd.maxY += bd.strokeWidth/2; | ||
521 | + } | ||
522 | + bd.minX = bd.Cx - bd.Rm; | ||
523 | + bd.maxX = bd.Cx + bd.Rm; | ||
524 | + } else if (options.gaugeType === 'horizontalBar') { | ||
525 | + bd.titleY = bd.baseY + 4 * bd.fontSizeFactor + | ||
526 | + (options.title === '' ? 0 : options.fontTitleSize * bd.fontSizeFactor); | ||
527 | + bd.titleBottom = bd.titleY + (options.title === '' ? 0 : 4) * bd.fontSizeFactor; | ||
528 | + | ||
529 | + bd.valueY = bd.titleBottom + | ||
530 | + (options.hideValue ? 0 : options.fontValueSize * bd.fontSizeFactor); | ||
531 | + | ||
532 | + bd.barTop = bd.valueY + 8 * bd.fontSizeFactor; | ||
533 | + bd.barBottom = bd.barTop + bd.strokeWidth; | ||
534 | + | ||
535 | + if (options.hideMinMax && options.label === '') { | ||
536 | + bd.labelY = bd.barBottom; | ||
537 | + bd.barLeft = bd.origBaseX + options.fontMinMaxSize/3 * bd.fontSizeFactor; | ||
538 | + bd.barRight = bd.origBaseX + w + /*bd.width*/ - options.fontMinMaxSize/3 * bd.fontSizeFactor; | ||
539 | + } else { | ||
540 | + context.font = CanvasGauges.drawings.font(options, 'MinMax', bd.fontSizeFactor); | ||
541 | + const minTextWidth = context.measureText(options.minValue+'').width; | ||
542 | + const maxTextWidth = context.measureText(options.maxValue+'').width; | ||
543 | + const maxW = Math.max(minTextWidth, maxTextWidth); | ||
544 | + bd.minX = bd.origBaseX + maxW/2 + options.fontMinMaxSize/3 * bd.fontSizeFactor; | ||
545 | + bd.maxX = bd.origBaseX + w + /*bd.width*/ - maxW/2 - options.fontMinMaxSize/3 * bd.fontSizeFactor; | ||
546 | + bd.barLeft = bd.minX; | ||
547 | + bd.barRight = bd.maxX; | ||
548 | + bd.labelY = bd.barBottom + (8 + options.fontLabelSize) * bd.fontSizeFactor; | ||
549 | + bd.minY = bd.maxY = bd.labelY; | ||
550 | + } | ||
551 | + } else if (options.gaugeType === 'verticalBar') { | ||
552 | + bd.titleY = bd.baseY + ((options.title === '' ? 0 : options.fontTitleSize) + 8) * bd.fontSizeFactor; | ||
553 | + bd.titleBottom = bd.titleY + (options.title === '' ? 0 : 4) * bd.fontSizeFactor; | ||
554 | + | ||
555 | + bd.valueY = bd.titleBottom + (options.hideValue ? 0 : options.fontValueSize * bd.fontSizeFactor); | ||
556 | + bd.barTop = bd.valueY + 8 * bd.fontSizeFactor; | ||
557 | + | ||
558 | + bd.labelY = bd.baseY + bd.height - 16; | ||
559 | + if (options.label === '') { | ||
560 | + bd.barBottom = bd.labelY; | ||
561 | + } else { | ||
562 | + bd.barBottom = bd.labelY - (8 + options.fontLabelSize) * bd.fontSizeFactor; | ||
563 | + } | ||
564 | + bd.minX = bd.maxX = | ||
565 | + bd.baseX + bd.width/2 + bd.strokeWidth/2 + options.fontMinMaxSize/3 * bd.fontSizeFactor; | ||
566 | + bd.minY = bd.barBottom; | ||
567 | + bd.maxY = bd.barTop; | ||
568 | + bd.fontMinMaxBaseline = 'middle'; | ||
569 | + bd.fontMinMaxAlign = 'left'; | ||
570 | + } | ||
571 | + | ||
572 | + if (options.dashThickness) { | ||
573 | + let circumference; | ||
574 | + if (options.gaugeType === 'donut') { | ||
575 | + circumference = Math.PI * bd.Rm * 2; | ||
576 | + } else if (options.gaugeType === 'arc') { | ||
577 | + circumference = Math.PI * bd.Rm; | ||
578 | + } else if (options.gaugeType === 'horizontalBar') { | ||
579 | + circumference = bd.barRight - bd.barLeft; | ||
580 | + } else if (options.gaugeType === 'verticalBar') { | ||
581 | + circumference = bd.barBottom - bd.barTop; | ||
582 | + } | ||
583 | + let dashCount = Math.floor(circumference / (options.dashThickness * bd.fontSizeFactor)); | ||
584 | + if (options.gaugeType === 'donut') { | ||
585 | + // tslint:disable-next-line:no-bitwise | ||
586 | + dashCount = (dashCount | 1) - 1; | ||
587 | + } else { | ||
588 | + // tslint:disable-next-line:no-bitwise | ||
589 | + dashCount = (dashCount - 1) | 1; | ||
590 | + } | ||
591 | + bd.dashLength = Math.ceil(circumference/dashCount); | ||
592 | + } | ||
593 | + | ||
594 | + return bd; | ||
595 | +} | ||
596 | + | ||
597 | +function determineFontHeight (options: CanvasDigitalGaugeOptions, target: string, baseSize: number): FontHeightInfo { | ||
598 | + const fontStyleStr = 'font-style:' + options['font' + target + 'Style'] + ';font-weight:' + | ||
599 | + options['font' + target + 'Weight'] + ';font-size:' + | ||
600 | + options['font' + target + 'Size'] * baseSize + 'px;font-family:' + | ||
601 | + options['font' + target]; | ||
602 | + let result = CanvasDigitalGauge.heightCache[fontStyleStr]; | ||
603 | + if (!result) { | ||
604 | + const fontStyle = { | ||
605 | + fontFamily: options['font' + target], | ||
606 | + fontSize: options['font' + target + 'Size'] * baseSize + 'px', | ||
607 | + fontWeight: options['font' + target + 'Weight'], | ||
608 | + fontStyle: options['font' + target + 'Style'] | ||
609 | + }; | ||
610 | + const text = $('<span>Hg</span>').css(fontStyle); | ||
611 | + const block = $('<div style="display: inline-block; width: 1px; height: 0px;"></div>'); | ||
612 | + | ||
613 | + const div = $('<div></div>'); | ||
614 | + div.append(text, block); | ||
615 | + | ||
616 | + const body = $('body'); | ||
617 | + body.append(div); | ||
618 | + | ||
619 | + try { | ||
620 | + result = {}; | ||
621 | + block.css({ verticalAlign: 'baseline' }); | ||
622 | + result.ascent = block.offset().top - text.offset().top; | ||
623 | + block.css({ verticalAlign: 'bottom' }); | ||
624 | + result.height = block.offset().top - text.offset().top; | ||
625 | + result.descent = result.height - result.ascent; | ||
626 | + } finally { | ||
627 | + div.remove(); | ||
628 | + } | ||
629 | + | ||
630 | + CanvasDigitalGauge.heightCache[fontStyleStr] = result; | ||
631 | + } | ||
632 | + return result; | ||
633 | +} | ||
634 | + | ||
635 | +function drawBackground(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions) { | ||
636 | + const {barLeft, barRight, barTop, barBottom, width, baseX, strokeWidth} = | ||
637 | + context.barDimensions; | ||
638 | + if (context.barDimensions.dashLength) { | ||
639 | + context.setLineDash([context.barDimensions.dashLength]); | ||
640 | + } | ||
641 | + context.beginPath(); | ||
642 | + context.strokeStyle = options.gaugeColor; | ||
643 | + context.lineWidth = strokeWidth; | ||
644 | + if (options.roundedLineCap) { | ||
645 | + context.lineCap = 'round'; | ||
646 | + } | ||
647 | + if (options.gaugeType === 'donut') { | ||
648 | + context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, | ||
649 | + options.donutStartAngle, options.donutEndAngle); | ||
650 | + context.stroke(); | ||
651 | + } else if (options.gaugeType === 'arc') { | ||
652 | + context.arc(context.barDimensions.Cx, context.barDimensions.Cy, | ||
653 | + context.barDimensions.Rm, Math.PI, 2*Math.PI); | ||
654 | + context.stroke(); | ||
655 | + } else if (options.gaugeType === 'horizontalBar') { | ||
656 | + context.moveTo(barLeft,barTop + strokeWidth/2); | ||
657 | + context.lineTo(barRight,barTop + strokeWidth/2); | ||
658 | + context.stroke(); | ||
659 | + } else if (options.gaugeType === 'verticalBar') { | ||
660 | + context.moveTo(baseX + width/2, barBottom); | ||
661 | + context.lineTo(baseX + width/2, barTop); | ||
662 | + context.stroke(); | ||
663 | + } | ||
664 | +} | ||
665 | + | ||
666 | +function drawText(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions, | ||
667 | + target: string, text: string, textX: number, textY: number) { | ||
668 | + context.fillStyle = options[(options.neonGlowBrightness ? 'neonColor' : 'color') + target]; | ||
669 | + context.fillText(text, textX, textY); | ||
670 | +} | ||
671 | + | ||
672 | +function drawDigitalTitle(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions) { | ||
673 | + if (!options.title || typeof options.title !== 'string') return; | ||
674 | + | ||
675 | + const {titleY, width, baseX, fontSizeFactor} = | ||
676 | + context.barDimensions; | ||
677 | + | ||
678 | + const textX = Math.round(baseX + width / 2); | ||
679 | + const textY = titleY; | ||
680 | + | ||
681 | + context.save(); | ||
682 | + context.textAlign = 'center'; | ||
683 | + context.font = CanvasGauges.drawings.font(options, 'Title', fontSizeFactor); | ||
684 | + context.lineWidth = 0; | ||
685 | + drawText(context, options, 'Title', options.title.toUpperCase(), textX, textY); | ||
686 | +} | ||
687 | + | ||
688 | +function drawDigitalLabel(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions) { | ||
689 | + if (!options.label || options.label === '') return; | ||
690 | + | ||
691 | + const {labelY, baseX, width, fontSizeFactor} = | ||
692 | + context.barDimensions; | ||
693 | + | ||
694 | + const textX = Math.round(baseX + width / 2); | ||
695 | + const textY = labelY; | ||
696 | + | ||
697 | + context.save(); | ||
698 | + context.textAlign = 'center'; | ||
699 | + context.font = CanvasGauges.drawings.font(options, 'Label', fontSizeFactor); | ||
700 | + context.lineWidth = 0; | ||
701 | + drawText(context, options, 'Label', options.label.toUpperCase(), textX, textY); | ||
702 | +} | ||
703 | + | ||
704 | +function drawDigitalMinMax(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions) { | ||
705 | + if (options.hideMinMax || options.gaugeType === 'donut') return; | ||
706 | + | ||
707 | + const {minY, maxY, minX, maxX, fontSizeFactor, fontMinMaxAlign, fontMinMaxBaseline} = | ||
708 | + context.barDimensions; | ||
709 | + | ||
710 | + context.save(); | ||
711 | + context.textAlign = fontMinMaxAlign; | ||
712 | + context.textBaseline = fontMinMaxBaseline; | ||
713 | + context.font = CanvasGauges.drawings.font(options, 'MinMax', fontSizeFactor); | ||
714 | + context.lineWidth = 0; | ||
715 | + drawText(context, options, 'MinMax', options.minValue+'', minX, minY); | ||
716 | + drawText(context, options, 'MinMax', options.maxValue+'', maxX, maxY); | ||
717 | +} | ||
718 | + | ||
719 | +function padValue(val: any, options: CanvasDigitalGaugeOptions): string { | ||
720 | + const dec = options.valueDec; | ||
721 | + let strVal; | ||
722 | + let n; | ||
723 | + | ||
724 | + val = parseFloat(val); | ||
725 | + n = (val < 0); | ||
726 | + val = Math.abs(val); | ||
727 | + | ||
728 | + if (dec > 0) { | ||
729 | + strVal = val.toFixed(dec).toString() | ||
730 | + } else { | ||
731 | + strVal = Math.round(val).toString(); | ||
732 | + } | ||
733 | + strVal = (n ? '-' : '') + strVal; | ||
734 | + return strVal; | ||
735 | +} | ||
736 | + | ||
737 | +function drawDigitalValue(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions, value: any) { | ||
738 | + if (options.hideValue) return; | ||
739 | + | ||
740 | + const {valueY, baseX, width, fontSizeFactor, fontValueBaseline} = | ||
741 | + context.barDimensions; | ||
742 | + | ||
743 | + const textX = Math.round(baseX + width / 2); | ||
744 | + const textY = valueY; | ||
745 | + | ||
746 | + let text = options.valueText || padValue(value, options); | ||
747 | + text += options.symbol; | ||
748 | + | ||
749 | + context.save(); | ||
750 | + context.textAlign = 'center'; | ||
751 | + context.textBaseline = fontValueBaseline; | ||
752 | + context.font = CanvasGauges.drawings.font(options, 'Value', fontSizeFactor); | ||
753 | + context.lineWidth = 0; | ||
754 | + drawText(context, options, 'Value', text, textX, textY); | ||
755 | +} | ||
756 | + | ||
757 | +function getProgressColor(progress: number, colorsRange: DigitalGaugeColorRange[]): string { | ||
758 | + | ||
759 | + if (progress === 0 || colorsRange.length === 1) { | ||
760 | + return colorsRange[0].rgbString; | ||
761 | + } | ||
762 | + | ||
763 | + for (let j = 0; j < colorsRange.length; j++) { | ||
764 | + if (progress <= colorsRange[j].pct) { | ||
765 | + const lower = colorsRange[j - 1]; | ||
766 | + const upper = colorsRange[j]; | ||
767 | + const range = upper.pct - lower.pct; | ||
768 | + const rangePct = (progress - lower.pct) / range; | ||
769 | + const pctLower = 1 - rangePct; | ||
770 | + const pctUpper = rangePct; | ||
771 | + const color = tinycolor({ | ||
772 | + r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper), | ||
773 | + g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper), | ||
774 | + b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper) | ||
775 | + }); | ||
776 | + return color.toRgbString(); | ||
777 | + } | ||
778 | + } | ||
779 | +} | ||
780 | + | ||
781 | +function drawArcGlow(context: DigitalGaugeCanvasRenderingContext2D, | ||
782 | + Cx: number, Cy: number, Ri: number, Rm: number, Ro: number, | ||
783 | + color: string, progress: number, isDonut: boolean, | ||
784 | + donutStartAngle?: number, donutEndAngle?: number) { | ||
785 | + context.setLineDash([]); | ||
786 | + const strokeWidth = Ro - Ri; | ||
787 | + const blur = 0.55; | ||
788 | + const edge = strokeWidth*blur; | ||
789 | + context.lineWidth = strokeWidth+edge; | ||
790 | + const stop = blur/(2*blur+2); | ||
791 | + const glowGradient = context.createRadialGradient(Cx,Cy,Ri-edge/2,Cx,Cy,Ro+edge/2); | ||
792 | + const color1 = tinycolor(color).setAlpha(0.5).toRgbString(); | ||
793 | + const color2 = tinycolor(color).setAlpha(0).toRgbString(); | ||
794 | + glowGradient.addColorStop(0,color2); | ||
795 | + glowGradient.addColorStop(stop,color1); | ||
796 | + glowGradient.addColorStop(1.0-stop,color1); | ||
797 | + glowGradient.addColorStop(1,color2); | ||
798 | + context.strokeStyle = glowGradient; | ||
799 | + context.beginPath(); | ||
800 | + const e = 0.01 * Math.PI; | ||
801 | + if (isDonut) { | ||
802 | + context.arc(Cx, Cy, Rm, donutStartAngle - e, donutStartAngle + | ||
803 | + (donutEndAngle - donutStartAngle) * progress + e); | ||
804 | + } else { | ||
805 | + context.arc(Cx, Cy, Rm, Math.PI - e, Math.PI + Math.PI * progress + e); | ||
806 | + } | ||
807 | + context.stroke(); | ||
808 | +} | ||
809 | + | ||
810 | +function drawBarGlow(context: DigitalGaugeCanvasRenderingContext2D, startX: number, startY: number, | ||
811 | + endX: number, endY: number, color: string, strokeWidth: number, isVertical: boolean) { | ||
812 | + context.setLineDash([]); | ||
813 | + const blur = 0.55; | ||
814 | + const edge = strokeWidth*blur; | ||
815 | + context.lineWidth = strokeWidth+edge; | ||
816 | + const stop = blur/(2*blur+2); | ||
817 | + const gradientStartX = isVertical ? startX - context.lineWidth/2 : 0; | ||
818 | + const gradientStartY = isVertical ? 0 : startY - context.lineWidth/2; | ||
819 | + const gradientStopX = isVertical ? startX + context.lineWidth/2 : 0; | ||
820 | + const gradientStopY = isVertical ? 0 : startY + context.lineWidth/2; | ||
821 | + | ||
822 | + const glowGradient = context.createLinearGradient(gradientStartX,gradientStartY,gradientStopX,gradientStopY); | ||
823 | + const color1 = tinycolor(color).setAlpha(0.5).toRgbString(); | ||
824 | + const color2 = tinycolor(color).setAlpha(0).toRgbString(); | ||
825 | + glowGradient.addColorStop(0,color2); | ||
826 | + glowGradient.addColorStop(stop,color1); | ||
827 | + glowGradient.addColorStop(1.0-stop,color1); | ||
828 | + glowGradient.addColorStop(1,color2); | ||
829 | + context.strokeStyle = glowGradient; | ||
830 | + const dx = isVertical ? 0 : 0.05 * context.lineWidth; | ||
831 | + const dy = isVertical ? 0.05 * context.lineWidth : 0; | ||
832 | + context.beginPath(); | ||
833 | + context.moveTo(startX - dx, startY + dy); | ||
834 | + context.lineTo(endX + dx, endY - dy); | ||
835 | + context.stroke(); | ||
836 | +} | ||
837 | + | ||
838 | +function drawProgress(context: DigitalGaugeCanvasRenderingContext2D, | ||
839 | + options: CanvasDigitalGaugeOptions, progress: number) { | ||
840 | + let neonColor; | ||
841 | + if (options.neonGlowBrightness) { | ||
842 | + context.currentColor = neonColor = getProgressColor(progress, options.neonColorsRange); | ||
843 | + } else { | ||
844 | + context.currentColor = context.strokeStyle = getProgressColor(progress, options.colorsRange); | ||
845 | + } | ||
846 | + | ||
847 | + const {barLeft, barRight, barTop, baseX, width, barBottom, Cx, Cy, Rm, Ro, Ri, strokeWidth} = | ||
848 | + context.barDimensions; | ||
849 | + | ||
850 | + if (context.barDimensions.dashLength) { | ||
851 | + context.setLineDash([context.barDimensions.dashLength]); | ||
852 | + } | ||
853 | + context.lineWidth = strokeWidth; | ||
854 | + if (options.roundedLineCap) { | ||
855 | + context.lineCap = 'round'; | ||
856 | + } else { | ||
857 | + context.lineCap = 'butt'; | ||
858 | + } | ||
859 | + if (options.gaugeType === 'donut') { | ||
860 | + if (options.neonGlowBrightness) { | ||
861 | + context.strokeStyle = neonColor; | ||
862 | + } | ||
863 | + context.beginPath(); | ||
864 | + context.arc(Cx, Cy, Rm, options.donutStartAngle, options.donutStartAngle + | ||
865 | + (options.donutEndAngle - options.donutStartAngle) * progress); | ||
866 | + context.stroke(); | ||
867 | + if (options.neonGlowBrightness && !options.isMobile) { | ||
868 | + drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, | ||
869 | + options.donutStartAngle, options.donutEndAngle); | ||
870 | + } | ||
871 | + } else if (options.gaugeType === 'arc') { | ||
872 | + if (options.neonGlowBrightness) { | ||
873 | + context.strokeStyle = neonColor; | ||
874 | + } | ||
875 | + context.beginPath(); | ||
876 | + context.arc(Cx, Cy, Rm, Math.PI, Math.PI + Math.PI * progress); | ||
877 | + context.stroke(); | ||
878 | + if (options.neonGlowBrightness && !options.isMobile) { | ||
879 | + drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, false); | ||
880 | + } | ||
881 | + } else if (options.gaugeType === 'horizontalBar') { | ||
882 | + if (options.neonGlowBrightness) { | ||
883 | + context.strokeStyle = neonColor; | ||
884 | + } | ||
885 | + context.beginPath(); | ||
886 | + context.moveTo(barLeft,barTop + strokeWidth/2); | ||
887 | + context.lineTo(barLeft + (barRight-barLeft)*progress, barTop + strokeWidth/2); | ||
888 | + context.stroke(); | ||
889 | + if (options.neonGlowBrightness && !options.isMobile) { | ||
890 | + drawBarGlow(context, barLeft, barTop + strokeWidth/2, | ||
891 | + barLeft + (barRight-barLeft)*progress, barTop + strokeWidth/2, | ||
892 | + neonColor, strokeWidth, false); | ||
893 | + } | ||
894 | + } else if (options.gaugeType === 'verticalBar') { | ||
895 | + if (options.neonGlowBrightness) { | ||
896 | + context.strokeStyle = neonColor; | ||
897 | + } | ||
898 | + context.beginPath(); | ||
899 | + context.moveTo(baseX + width/2, barBottom); | ||
900 | + context.lineTo(baseX + width/2, barBottom - (barBottom-barTop)*progress); | ||
901 | + context.stroke(); | ||
902 | + if (options.neonGlowBrightness && !options.isMobile) { | ||
903 | + drawBarGlow(context, baseX + width/2, barBottom, | ||
904 | + baseX + width/2, barBottom - (barBottom-barTop)*progress, | ||
905 | + neonColor, strokeWidth, true); | ||
906 | + } | ||
907 | + } | ||
908 | + | ||
909 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { JsonSettingsSchema } from '@shared/models/widget.models'; | ||
18 | +import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; | ||
19 | +import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models'; | ||
20 | +import { FontSettings } from '@home/components/widget/lib/settings.models'; | ||
21 | + | ||
22 | +export interface DigitalGaugeSettings { | ||
23 | + minValue?: number; | ||
24 | + maxValue?: number; | ||
25 | + gaugeType?: GaugeType; | ||
26 | + donutStartAngle?: number; | ||
27 | + neonGlowBrightness?: number; | ||
28 | + dashThickness?: number; | ||
29 | + roundedLineCap?: boolean; | ||
30 | + title?: string; | ||
31 | + showTitle?: boolean; | ||
32 | + unitTitle?: string; | ||
33 | + showUnitTitle?: boolean; | ||
34 | + showTimestamp?: boolean; | ||
35 | + timestampFormat?: string; | ||
36 | + showValue?: boolean; | ||
37 | + showMinMax?: boolean; | ||
38 | + gaugeWidthScale?: number; | ||
39 | + defaultColor?: string; | ||
40 | + gaugeColor?: string; | ||
41 | + levelColors?: string[]; | ||
42 | + animation?: boolean; | ||
43 | + animationDuration?: number; | ||
44 | + animationRule?: AnimationRule; | ||
45 | + titleFont?: FontSettings; | ||
46 | + labelFont?: FontSettings; | ||
47 | + valueFont?: FontSettings; | ||
48 | + minMaxFont?: FontSettings; | ||
49 | + decimals?: number; | ||
50 | + units?: string; | ||
51 | + hideValue?: boolean; | ||
52 | + hideMinMax?: boolean; | ||
53 | +} | ||
54 | + | ||
55 | +export const digitalGaugeSettingsSchema: JsonSettingsSchema = { | ||
56 | + schema: { | ||
57 | + type: 'object', | ||
58 | + title: 'Settings', | ||
59 | + properties: { | ||
60 | + minValue: { | ||
61 | + title: 'Minimum value', | ||
62 | + type: 'number', | ||
63 | + default: 0 | ||
64 | + }, | ||
65 | + maxValue: { | ||
66 | + title: 'Maximum value', | ||
67 | + type: 'number', | ||
68 | + default: 100 | ||
69 | + }, | ||
70 | + gaugeType: { | ||
71 | + title: 'Gauge type', | ||
72 | + type: 'string', | ||
73 | + default: 'arc' | ||
74 | + }, | ||
75 | + donutStartAngle: { | ||
76 | + title: 'Angle to start from when in donut mode', | ||
77 | + type: 'number', | ||
78 | + default: 90 | ||
79 | + }, | ||
80 | + neonGlowBrightness: { | ||
81 | + title: 'Neon glow effect brightness, (0-100), 0 - disable effect', | ||
82 | + type: 'number', | ||
83 | + default: 0 | ||
84 | + }, | ||
85 | + dashThickness: { | ||
86 | + title: 'Thickness of the stripes, 0 - no stripes', | ||
87 | + type: 'number', | ||
88 | + default: 0 | ||
89 | + }, | ||
90 | + roundedLineCap: { | ||
91 | + title: 'Display rounded line cap', | ||
92 | + type: 'boolean', | ||
93 | + default: false | ||
94 | + }, | ||
95 | + title: { | ||
96 | + title: 'Gauge title', | ||
97 | + type: 'string', | ||
98 | + default: null | ||
99 | + }, | ||
100 | + showTitle: { | ||
101 | + title: 'Show gauge title', | ||
102 | + type: 'boolean', | ||
103 | + default: false | ||
104 | + }, | ||
105 | + unitTitle: { | ||
106 | + title: 'Unit title', | ||
107 | + type: 'string', | ||
108 | + default: null | ||
109 | + }, | ||
110 | + showUnitTitle: { | ||
111 | + title: 'Show unit title', | ||
112 | + type: 'boolean', | ||
113 | + default: false | ||
114 | + }, | ||
115 | + showTimestamp: { | ||
116 | + title: 'Show value timestamp', | ||
117 | + type: 'boolean', | ||
118 | + default: false | ||
119 | + }, | ||
120 | + timestampFormat: { | ||
121 | + title: 'Timestamp format', | ||
122 | + type: 'string', | ||
123 | + default: 'yyyy-MM-dd HH:mm:ss' | ||
124 | + }, | ||
125 | + showValue: { | ||
126 | + title: 'Show value text', | ||
127 | + type: 'boolean', | ||
128 | + default: true | ||
129 | + }, | ||
130 | + showMinMax: { | ||
131 | + title: 'Show min and max values', | ||
132 | + type: 'boolean', | ||
133 | + default: true | ||
134 | + }, | ||
135 | + gaugeWidthScale: { | ||
136 | + title: 'Width of the gauge element', | ||
137 | + type: 'number', | ||
138 | + default: 0.75 | ||
139 | + }, | ||
140 | + defaultColor: { | ||
141 | + title: 'Default color', | ||
142 | + type: 'string', | ||
143 | + default: null | ||
144 | + }, | ||
145 | + gaugeColor: { | ||
146 | + title: 'Background color of the gauge element', | ||
147 | + type: 'string', | ||
148 | + default: null | ||
149 | + }, | ||
150 | + levelColors: { | ||
151 | + title: 'Colors of indicator, from lower to upper', | ||
152 | + type: 'array', | ||
153 | + items: { | ||
154 | + title: 'Color', | ||
155 | + type: 'string' | ||
156 | + } | ||
157 | + }, | ||
158 | + animation: { | ||
159 | + title: 'Enable animation', | ||
160 | + type: 'boolean', | ||
161 | + default: true | ||
162 | + }, | ||
163 | + animationDuration: { | ||
164 | + title: 'Animation duration', | ||
165 | + type: 'number', | ||
166 | + default: 500 | ||
167 | + }, | ||
168 | + animationRule: { | ||
169 | + title: 'Animation rule', | ||
170 | + type: 'string', | ||
171 | + default: 'linear' | ||
172 | + }, | ||
173 | + titleFont: { | ||
174 | + title: 'Gauge title font', | ||
175 | + type: 'object', | ||
176 | + properties: { | ||
177 | + family: { | ||
178 | + title: 'Font family', | ||
179 | + type: 'string', | ||
180 | + default: 'Roboto' | ||
181 | + }, | ||
182 | + size: { | ||
183 | + title: 'Size', | ||
184 | + type: 'number', | ||
185 | + default: 12 | ||
186 | + }, | ||
187 | + style: { | ||
188 | + title: 'Style', | ||
189 | + type: 'string', | ||
190 | + default: 'normal' | ||
191 | + }, | ||
192 | + weight: { | ||
193 | + title: 'Weight', | ||
194 | + type: 'string', | ||
195 | + default: '500' | ||
196 | + }, | ||
197 | + color: { | ||
198 | + title: 'color', | ||
199 | + type: 'string', | ||
200 | + default: null | ||
201 | + } | ||
202 | + } | ||
203 | + }, | ||
204 | + labelFont: { | ||
205 | + title: 'Font of label showing under value', | ||
206 | + type: 'object', | ||
207 | + properties: { | ||
208 | + family: { | ||
209 | + title: 'Font family', | ||
210 | + type: 'string', | ||
211 | + default: 'Roboto' | ||
212 | + }, | ||
213 | + size: { | ||
214 | + title: 'Size', | ||
215 | + type: 'number', | ||
216 | + default: 8 | ||
217 | + }, | ||
218 | + style: { | ||
219 | + title: 'Style', | ||
220 | + type: 'string', | ||
221 | + default: 'normal' | ||
222 | + }, | ||
223 | + weight: { | ||
224 | + title: 'Weight', | ||
225 | + type: 'string', | ||
226 | + default: '500' | ||
227 | + }, | ||
228 | + color: { | ||
229 | + title: 'color', | ||
230 | + type: 'string', | ||
231 | + default: null | ||
232 | + } | ||
233 | + } | ||
234 | + }, | ||
235 | + valueFont: { | ||
236 | + title: 'Font of label showing current value', | ||
237 | + type: 'object', | ||
238 | + properties: { | ||
239 | + family: { | ||
240 | + title: 'Font family', | ||
241 | + type: 'string', | ||
242 | + default: 'Roboto' | ||
243 | + }, | ||
244 | + size: { | ||
245 | + title: 'Size', | ||
246 | + type: 'number', | ||
247 | + default: 18 | ||
248 | + }, | ||
249 | + style: { | ||
250 | + title: 'Style', | ||
251 | + type: 'string', | ||
252 | + default: 'normal' | ||
253 | + }, | ||
254 | + weight: { | ||
255 | + title: 'Weight', | ||
256 | + type: 'string', | ||
257 | + default: '500' | ||
258 | + }, | ||
259 | + color: { | ||
260 | + title: 'color', | ||
261 | + type: 'string', | ||
262 | + default: null | ||
263 | + } | ||
264 | + } | ||
265 | + }, | ||
266 | + minMaxFont: { | ||
267 | + title: 'Font of minimum and maximum labels', | ||
268 | + type: 'object', | ||
269 | + properties: { | ||
270 | + family: { | ||
271 | + title: 'Font family', | ||
272 | + type: 'string', | ||
273 | + default: 'Roboto' | ||
274 | + }, | ||
275 | + size: { | ||
276 | + title: 'Size', | ||
277 | + type: 'number', | ||
278 | + default: 10 | ||
279 | + }, | ||
280 | + style: { | ||
281 | + title: 'Style', | ||
282 | + type: 'string', | ||
283 | + default: 'normal' | ||
284 | + }, | ||
285 | + weight: { | ||
286 | + title: 'Weight', | ||
287 | + type: 'string', | ||
288 | + default: '500' | ||
289 | + }, | ||
290 | + color: { | ||
291 | + title: 'color', | ||
292 | + type: 'string', | ||
293 | + default: null | ||
294 | + } | ||
295 | + } | ||
296 | + } | ||
297 | + } | ||
298 | + }, | ||
299 | + form: [ | ||
300 | + 'minValue', | ||
301 | + 'maxValue', | ||
302 | + { | ||
303 | + key: 'gaugeType', | ||
304 | + type: 'rc-select', | ||
305 | + multiple: false, | ||
306 | + items: [ | ||
307 | + { | ||
308 | + value: 'arc', | ||
309 | + label: 'Arc' | ||
310 | + }, | ||
311 | + { | ||
312 | + value: 'donut', | ||
313 | + label: 'Donut' | ||
314 | + }, | ||
315 | + { | ||
316 | + value: 'horizontalBar', | ||
317 | + label: 'Horizontal bar' | ||
318 | + }, | ||
319 | + { | ||
320 | + value: 'verticalBar', | ||
321 | + label: 'Vertical bar' | ||
322 | + } | ||
323 | + ] | ||
324 | + }, | ||
325 | + 'donutStartAngle', | ||
326 | + 'neonGlowBrightness', | ||
327 | + 'dashThickness', | ||
328 | + 'roundedLineCap', | ||
329 | + 'title', | ||
330 | + 'showTitle', | ||
331 | + 'unitTitle', | ||
332 | + 'showUnitTitle', | ||
333 | + 'showTimestamp', | ||
334 | + 'timestampFormat', | ||
335 | + 'showValue', | ||
336 | + 'showMinMax', | ||
337 | + 'gaugeWidthScale', | ||
338 | + { | ||
339 | + key: 'defaultColor', | ||
340 | + type: 'color' | ||
341 | + }, | ||
342 | + { | ||
343 | + key: 'gaugeColor', | ||
344 | + type: 'color' | ||
345 | + }, | ||
346 | + { | ||
347 | + key: 'levelColors', | ||
348 | + items: [ | ||
349 | + { | ||
350 | + key: 'levelColors[]', | ||
351 | + type: 'color' | ||
352 | + } | ||
353 | + ] | ||
354 | + }, | ||
355 | + 'animation', | ||
356 | + 'animationDuration', | ||
357 | + { | ||
358 | + key: 'animationRule', | ||
359 | + type: 'rc-select', | ||
360 | + multiple: false, | ||
361 | + items: [ | ||
362 | + { | ||
363 | + value: 'linear', | ||
364 | + label: 'Linear' | ||
365 | + }, | ||
366 | + { | ||
367 | + value: 'quad', | ||
368 | + label: 'Quad' | ||
369 | + }, | ||
370 | + { | ||
371 | + value: 'quint', | ||
372 | + label: 'Quint' | ||
373 | + }, | ||
374 | + { | ||
375 | + value: 'cycle', | ||
376 | + label: 'Cycle' | ||
377 | + }, | ||
378 | + { | ||
379 | + value: 'bounce', | ||
380 | + label: 'Bounce' | ||
381 | + }, | ||
382 | + { | ||
383 | + value: 'elastic', | ||
384 | + label: 'Elastic' | ||
385 | + }, | ||
386 | + { | ||
387 | + value: 'dequad', | ||
388 | + label: 'Dequad' | ||
389 | + }, | ||
390 | + { | ||
391 | + value: 'dequint', | ||
392 | + label: 'Dequint' | ||
393 | + }, | ||
394 | + { | ||
395 | + value: 'decycle', | ||
396 | + label: 'Decycle' | ||
397 | + }, | ||
398 | + { | ||
399 | + value: 'debounce', | ||
400 | + label: 'Debounce' | ||
401 | + }, | ||
402 | + { | ||
403 | + value: 'delastic', | ||
404 | + label: 'Delastic' | ||
405 | + } | ||
406 | + ] | ||
407 | + }, | ||
408 | + { | ||
409 | + key: 'titleFont', | ||
410 | + items: [ | ||
411 | + 'titleFont.family', | ||
412 | + 'titleFont.size', | ||
413 | + { | ||
414 | + key: 'titleFont.style', | ||
415 | + type: 'rc-select', | ||
416 | + multiple: false, | ||
417 | + items: [ | ||
418 | + { | ||
419 | + value: 'normal', | ||
420 | + label: 'Normal' | ||
421 | + }, | ||
422 | + { | ||
423 | + value: 'italic', | ||
424 | + label: 'Italic' | ||
425 | + }, | ||
426 | + { | ||
427 | + value: 'oblique', | ||
428 | + label: 'Oblique' | ||
429 | + } | ||
430 | + ] | ||
431 | + }, | ||
432 | + { | ||
433 | + key: 'titleFont.weight', | ||
434 | + type: 'rc-select', | ||
435 | + multiple: false, | ||
436 | + items: [ | ||
437 | + { | ||
438 | + value: 'normal', | ||
439 | + label: 'Normal' | ||
440 | + }, | ||
441 | + { | ||
442 | + value: 'bold', | ||
443 | + label: 'Bold' | ||
444 | + }, | ||
445 | + { | ||
446 | + value: 'bolder', | ||
447 | + label: 'Bolder' | ||
448 | + }, | ||
449 | + { | ||
450 | + value: 'lighter', | ||
451 | + label: 'Lighter' | ||
452 | + }, | ||
453 | + { | ||
454 | + value: '100', | ||
455 | + label: '100' | ||
456 | + }, | ||
457 | + { | ||
458 | + value: '200', | ||
459 | + label: '200' | ||
460 | + }, | ||
461 | + { | ||
462 | + value: '300', | ||
463 | + label: '300' | ||
464 | + }, | ||
465 | + { | ||
466 | + value: '400', | ||
467 | + label: '400' | ||
468 | + }, | ||
469 | + { | ||
470 | + value: '500', | ||
471 | + label: '500' | ||
472 | + }, | ||
473 | + { | ||
474 | + value: '600', | ||
475 | + label: '600' | ||
476 | + }, | ||
477 | + { | ||
478 | + value: '700', | ||
479 | + label: '800' | ||
480 | + }, | ||
481 | + { | ||
482 | + value: '800', | ||
483 | + label: '800' | ||
484 | + }, | ||
485 | + { | ||
486 | + value: '900', | ||
487 | + label: '900' | ||
488 | + } | ||
489 | + ] | ||
490 | + }, | ||
491 | + { | ||
492 | + key: 'titleFont.color', | ||
493 | + type: 'color' | ||
494 | + } | ||
495 | + ] | ||
496 | + }, | ||
497 | + { | ||
498 | + key: 'labelFont', | ||
499 | + items: [ | ||
500 | + 'labelFont.family', | ||
501 | + 'labelFont.size', | ||
502 | + { | ||
503 | + key: 'labelFont.style', | ||
504 | + type: 'rc-select', | ||
505 | + multiple: false, | ||
506 | + items: [ | ||
507 | + { | ||
508 | + value: 'normal', | ||
509 | + label: 'Normal' | ||
510 | + }, | ||
511 | + { | ||
512 | + value: 'italic', | ||
513 | + label: 'Italic' | ||
514 | + }, | ||
515 | + { | ||
516 | + value: 'oblique', | ||
517 | + label: 'Oblique' | ||
518 | + } | ||
519 | + ] | ||
520 | + }, | ||
521 | + { | ||
522 | + key: 'labelFont.weight', | ||
523 | + type: 'rc-select', | ||
524 | + multiple: false, | ||
525 | + items: [ | ||
526 | + { | ||
527 | + value: 'normal', | ||
528 | + label: 'Normal' | ||
529 | + }, | ||
530 | + { | ||
531 | + value: 'bold', | ||
532 | + label: 'Bold' | ||
533 | + }, | ||
534 | + { | ||
535 | + value: 'bolder', | ||
536 | + label: 'Bolder' | ||
537 | + }, | ||
538 | + { | ||
539 | + value: 'lighter', | ||
540 | + label: 'Lighter' | ||
541 | + }, | ||
542 | + { | ||
543 | + value: '100', | ||
544 | + label: '100' | ||
545 | + }, | ||
546 | + { | ||
547 | + value: '200', | ||
548 | + label: '200' | ||
549 | + }, | ||
550 | + { | ||
551 | + value: '300', | ||
552 | + label: '300' | ||
553 | + }, | ||
554 | + { | ||
555 | + value: '400', | ||
556 | + label: '400' | ||
557 | + }, | ||
558 | + { | ||
559 | + value: '500', | ||
560 | + label: '500' | ||
561 | + }, | ||
562 | + { | ||
563 | + value: '600', | ||
564 | + label: '600' | ||
565 | + }, | ||
566 | + { | ||
567 | + value: '700', | ||
568 | + label: '800' | ||
569 | + }, | ||
570 | + { | ||
571 | + value: '800', | ||
572 | + label: '800' | ||
573 | + }, | ||
574 | + { | ||
575 | + value: '900', | ||
576 | + label: '900' | ||
577 | + } | ||
578 | + ] | ||
579 | + }, | ||
580 | + { | ||
581 | + key: 'labelFont.color', | ||
582 | + type: 'color' | ||
583 | + } | ||
584 | + ] | ||
585 | + }, | ||
586 | + { | ||
587 | + key: 'valueFont', | ||
588 | + items: [ | ||
589 | + 'valueFont.family', | ||
590 | + 'valueFont.size', | ||
591 | + { | ||
592 | + key: 'valueFont.style', | ||
593 | + type: 'rc-select', | ||
594 | + multiple: false, | ||
595 | + items: [ | ||
596 | + { | ||
597 | + value: 'normal', | ||
598 | + label: 'Normal' | ||
599 | + }, | ||
600 | + { | ||
601 | + value: 'italic', | ||
602 | + label: 'Italic' | ||
603 | + }, | ||
604 | + { | ||
605 | + value: 'oblique', | ||
606 | + label: 'Oblique' | ||
607 | + } | ||
608 | + ] | ||
609 | + }, | ||
610 | + { | ||
611 | + key: 'valueFont.weight', | ||
612 | + type: 'rc-select', | ||
613 | + multiple: false, | ||
614 | + items: [ | ||
615 | + { | ||
616 | + value: 'normal', | ||
617 | + label: 'Normal' | ||
618 | + }, | ||
619 | + { | ||
620 | + value: 'bold', | ||
621 | + label: 'Bold' | ||
622 | + }, | ||
623 | + { | ||
624 | + value: 'bolder', | ||
625 | + label: 'Bolder' | ||
626 | + }, | ||
627 | + { | ||
628 | + value: 'lighter', | ||
629 | + label: 'Lighter' | ||
630 | + }, | ||
631 | + { | ||
632 | + value: '100', | ||
633 | + label: '100' | ||
634 | + }, | ||
635 | + { | ||
636 | + value: '200', | ||
637 | + label: '200' | ||
638 | + }, | ||
639 | + { | ||
640 | + value: '300', | ||
641 | + label: '300' | ||
642 | + }, | ||
643 | + { | ||
644 | + value: '400', | ||
645 | + label: '400' | ||
646 | + }, | ||
647 | + { | ||
648 | + value: '500', | ||
649 | + label: '500' | ||
650 | + }, | ||
651 | + { | ||
652 | + value: '600', | ||
653 | + label: '600' | ||
654 | + }, | ||
655 | + { | ||
656 | + value: '700', | ||
657 | + label: '800' | ||
658 | + }, | ||
659 | + { | ||
660 | + value: '800', | ||
661 | + label: '800' | ||
662 | + }, | ||
663 | + { | ||
664 | + value: '900', | ||
665 | + label: '900' | ||
666 | + } | ||
667 | + ] | ||
668 | + }, | ||
669 | + { | ||
670 | + key: 'valueFont.color', | ||
671 | + type: 'color' | ||
672 | + } | ||
673 | + ] | ||
674 | + }, | ||
675 | + { | ||
676 | + key: 'minMaxFont', | ||
677 | + items: [ | ||
678 | + 'minMaxFont.family', | ||
679 | + 'minMaxFont.size', | ||
680 | + { | ||
681 | + key: 'minMaxFont.style', | ||
682 | + type: 'rc-select', | ||
683 | + multiple: false, | ||
684 | + items: [ | ||
685 | + { | ||
686 | + value: 'normal', | ||
687 | + label: 'Normal' | ||
688 | + }, | ||
689 | + { | ||
690 | + value: 'italic', | ||
691 | + label: 'Italic' | ||
692 | + }, | ||
693 | + { | ||
694 | + value: 'oblique', | ||
695 | + label: 'Oblique' | ||
696 | + } | ||
697 | + ] | ||
698 | + }, | ||
699 | + { | ||
700 | + key: 'minMaxFont.weight', | ||
701 | + type: 'rc-select', | ||
702 | + multiple: false, | ||
703 | + items: [ | ||
704 | + { | ||
705 | + value: 'normal', | ||
706 | + label: 'Normal' | ||
707 | + }, | ||
708 | + { | ||
709 | + value: 'bold', | ||
710 | + label: 'Bold' | ||
711 | + }, | ||
712 | + { | ||
713 | + value: 'bolder', | ||
714 | + label: 'Bolder' | ||
715 | + }, | ||
716 | + { | ||
717 | + value: 'lighter', | ||
718 | + label: 'Lighter' | ||
719 | + }, | ||
720 | + { | ||
721 | + value: '100', | ||
722 | + label: '100' | ||
723 | + }, | ||
724 | + { | ||
725 | + value: '200', | ||
726 | + label: '200' | ||
727 | + }, | ||
728 | + { | ||
729 | + value: '300', | ||
730 | + label: '300' | ||
731 | + }, | ||
732 | + { | ||
733 | + value: '400', | ||
734 | + label: '400' | ||
735 | + }, | ||
736 | + { | ||
737 | + value: '500', | ||
738 | + label: '500' | ||
739 | + }, | ||
740 | + { | ||
741 | + value: '600', | ||
742 | + label: '600' | ||
743 | + }, | ||
744 | + { | ||
745 | + value: '700', | ||
746 | + label: '800' | ||
747 | + }, | ||
748 | + { | ||
749 | + value: '800', | ||
750 | + label: '800' | ||
751 | + }, | ||
752 | + { | ||
753 | + value: '900', | ||
754 | + label: '900' | ||
755 | + } | ||
756 | + ] | ||
757 | + }, | ||
758 | + { | ||
759 | + key: 'minMaxFont.color', | ||
760 | + type: 'color' | ||
761 | + } | ||
762 | + ] | ||
763 | + } | ||
764 | + ] | ||
765 | +}; |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import * as CanvasGauges from 'canvas-gauges'; | ||
18 | +import { WidgetContext } from '@home/models/widget-component.models'; | ||
19 | +import { DigitalGaugeSettings, digitalGaugeSettingsSchema } from '@home/components/widget/lib/digital-gauge.models'; | ||
20 | +import * as tinycolor_ from 'tinycolor2'; | ||
21 | +import { isDefined } from '@core/utils'; | ||
22 | +import { prepareFontSettings } from '@home/components/widget/lib/settings.models'; | ||
23 | +import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge'; | ||
24 | +import { DatePipe } from '@angular/common'; | ||
25 | +import { JsonSettingsSchema } from '@shared/models/widget.models'; | ||
26 | +import GenericOptions = CanvasGauges.GenericOptions; | ||
27 | + | ||
28 | +const tinycolor = tinycolor_; | ||
29 | + | ||
30 | +const digitalGaugeSettingsSchemaValue = digitalGaugeSettingsSchema; | ||
31 | + | ||
32 | +export class TbCanvasDigitalGauge { | ||
33 | + | ||
34 | + private localSettings: DigitalGaugeSettings; | ||
35 | + | ||
36 | + private gauge: CanvasDigitalGauge; | ||
37 | + | ||
38 | + static get settingsSchema(): JsonSettingsSchema { | ||
39 | + return digitalGaugeSettingsSchemaValue; | ||
40 | + } | ||
41 | + | ||
42 | + constructor(protected ctx: WidgetContext, canvasId: string) { | ||
43 | + const gaugeElement = $('#'+canvasId, ctx.$container)[0]; | ||
44 | + const settings: DigitalGaugeSettings = ctx.settings; | ||
45 | + | ||
46 | + this.localSettings = {}; | ||
47 | + this.localSettings.minValue = settings.minValue || 0; | ||
48 | + this.localSettings.maxValue = settings.maxValue || 100; | ||
49 | + this.localSettings.gaugeType = settings.gaugeType || 'arc'; | ||
50 | + this.localSettings.neonGlowBrightness = settings.neonGlowBrightness || 0; | ||
51 | + this.localSettings.dashThickness = settings.dashThickness || 0; | ||
52 | + this.localSettings.roundedLineCap = settings.roundedLineCap === true; | ||
53 | + | ||
54 | + const dataKey = ctx.data[0].dataKey; | ||
55 | + const keyColor = settings.defaultColor || dataKey.color; | ||
56 | + | ||
57 | + this.localSettings.unitTitle = ((settings.showUnitTitle === true) ? | ||
58 | + (settings.unitTitle && settings.unitTitle.length > 0 ? | ||
59 | + settings.unitTitle : dataKey.label) : ''); | ||
60 | + | ||
61 | + this.localSettings.showTimestamp = settings.showTimestamp === true; | ||
62 | + this.localSettings.timestampFormat = settings.timestampFormat && settings.timestampFormat.length ? | ||
63 | + settings.timestampFormat : 'yyyy-MM-dd HH:mm:ss'; | ||
64 | + | ||
65 | + this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75; | ||
66 | + this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString(); | ||
67 | + | ||
68 | + if (!settings.levelColors || settings.levelColors.length <= 0) { | ||
69 | + this.localSettings.levelColors = [keyColor]; | ||
70 | + } else { | ||
71 | + this.localSettings.levelColors = settings.levelColors.slice(); | ||
72 | + } | ||
73 | + | ||
74 | + this.localSettings.decimals = isDefined(dataKey.decimals) ? dataKey.decimals : | ||
75 | + ((isDefined(settings.decimals) && settings.decimals !== null) | ||
76 | + ? settings.decimals : ctx.decimals); | ||
77 | + | ||
78 | + this.localSettings.units = dataKey.units && dataKey.units.length ? dataKey.units : | ||
79 | + (isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units); | ||
80 | + | ||
81 | + this.localSettings.hideValue = settings.showValue !== true; | ||
82 | + this.localSettings.hideMinMax = settings.showMinMax !== true; | ||
83 | + | ||
84 | + this.localSettings.title = ((settings.showTitle === true) ? | ||
85 | + (settings.title && settings.title.length > 0 ? | ||
86 | + settings.title : dataKey.label) : ''); | ||
87 | + | ||
88 | + if (!this.localSettings.unitTitle && this.localSettings.showTimestamp) { | ||
89 | + this.localSettings.unitTitle = ' '; | ||
90 | + } | ||
91 | + | ||
92 | + this.localSettings.titleFont = prepareFontSettings(settings.titleFont, { | ||
93 | + size: 12, | ||
94 | + style: 'normal', | ||
95 | + weight: '500', | ||
96 | + color: keyColor | ||
97 | + }); | ||
98 | + | ||
99 | + this.localSettings.valueFont = prepareFontSettings(settings.valueFont, { | ||
100 | + size: 18, | ||
101 | + style: 'normal', | ||
102 | + weight: '500', | ||
103 | + color: keyColor | ||
104 | + }); | ||
105 | + | ||
106 | + this.localSettings.minMaxFont = prepareFontSettings(settings.minMaxFont, { | ||
107 | + size: 10, | ||
108 | + style: 'normal', | ||
109 | + weight: '500', | ||
110 | + color: keyColor | ||
111 | + }); | ||
112 | + | ||
113 | + this.localSettings.labelFont = prepareFontSettings(settings.labelFont, { | ||
114 | + size: 8, | ||
115 | + style: 'normal', | ||
116 | + weight: '500', | ||
117 | + color: keyColor | ||
118 | + }); | ||
119 | + | ||
120 | + const gaugeData: CanvasDigitalGaugeOptions = { | ||
121 | + renderTo: gaugeElement, | ||
122 | + | ||
123 | + gaugeWidthScale: this.localSettings.gaugeWidthScale, | ||
124 | + gaugeColor: this.localSettings.gaugeColor, | ||
125 | + levelColors: this.localSettings.levelColors, | ||
126 | + | ||
127 | + title: this.localSettings.title, | ||
128 | + | ||
129 | + fontTitleSize: this.localSettings.titleFont.size, | ||
130 | + fontTitleStyle: this.localSettings.titleFont.style, | ||
131 | + fontTitleWeight: this.localSettings.titleFont.weight, | ||
132 | + colorTitle: this.localSettings.titleFont.color, | ||
133 | + fontTitle: this.localSettings.titleFont.family, | ||
134 | + | ||
135 | + fontValueSize: this.localSettings.valueFont.size, | ||
136 | + fontValueStyle: this.localSettings.valueFont.style, | ||
137 | + fontValueWeight: this.localSettings.valueFont.weight, | ||
138 | + colorValue: this.localSettings.valueFont.color, | ||
139 | + fontValue: this.localSettings.valueFont.family, | ||
140 | + | ||
141 | + fontMinMaxSize: this.localSettings.minMaxFont.size, | ||
142 | + fontMinMaxStyle: this.localSettings.minMaxFont.style, | ||
143 | + fontMinMaxWeight: this.localSettings.minMaxFont.weight, | ||
144 | + colorMinMax: this.localSettings.minMaxFont.color, | ||
145 | + fontMinMax: this.localSettings.minMaxFont.family, | ||
146 | + | ||
147 | + fontLabelSize: this.localSettings.labelFont.size, | ||
148 | + fontLabelStyle: this.localSettings.labelFont.style, | ||
149 | + fontLabelWeight: this.localSettings.labelFont.weight, | ||
150 | + colorLabel: this.localSettings.labelFont.color, | ||
151 | + fontLabel: this.localSettings.labelFont.family, | ||
152 | + | ||
153 | + minValue: this.localSettings.minValue, | ||
154 | + maxValue: this.localSettings.maxValue, | ||
155 | + gaugeType: this.localSettings.gaugeType, | ||
156 | + dashThickness: this.localSettings.dashThickness, | ||
157 | + roundedLineCap: this.localSettings.roundedLineCap, | ||
158 | + | ||
159 | + symbol: this.localSettings.units, | ||
160 | + label: this.localSettings.unitTitle, | ||
161 | + showTimestamp: this.localSettings.showTimestamp, | ||
162 | + hideValue: this.localSettings.hideValue, | ||
163 | + hideMinMax: this.localSettings.hideMinMax, | ||
164 | + | ||
165 | + valueDec: this.localSettings.decimals, | ||
166 | + | ||
167 | + neonGlowBrightness: this.localSettings.neonGlowBrightness, | ||
168 | + | ||
169 | + // animations | ||
170 | + animation: settings.animation !== false && !ctx.isMobile, | ||
171 | + animationDuration: (isDefined(settings.animationDuration) && settings.animationDuration !== null) | ||
172 | + ? settings.animationDuration : 500, | ||
173 | + animationRule: settings.animationRule || 'linear', | ||
174 | + | ||
175 | + isMobile: ctx.isMobile | ||
176 | + }; | ||
177 | + | ||
178 | + this.gauge = new CanvasDigitalGauge(gaugeData).draw(); | ||
179 | + } | ||
180 | + | ||
181 | + update() { | ||
182 | + if (this.ctx.data.length > 0) { | ||
183 | + const cellData = this.ctx.data[0]; | ||
184 | + if (cellData.data.length > 0) { | ||
185 | + const tvPair = cellData.data[cellData.data.length - | ||
186 | + 1]; | ||
187 | + let timestamp; | ||
188 | + if (this.localSettings.showTimestamp) { | ||
189 | + timestamp = tvPair[0]; | ||
190 | + const filter = this.ctx.$injector.get(DatePipe); | ||
191 | + (this.gauge.options as CanvasDigitalGaugeOptions).label = | ||
192 | + filter.transform(timestamp, this.localSettings.timestampFormat); | ||
193 | + } | ||
194 | + const value = tvPair[1]; | ||
195 | + if(value !== this.gauge.value) { | ||
196 | + this.gauge._value = value; | ||
197 | + this.gauge.value = value; | ||
198 | + } else if (this.localSettings.showTimestamp && this.gauge.timestamp !== timestamp) { | ||
199 | + this.gauge.timestamp = timestamp; | ||
200 | + } | ||
201 | + } | ||
202 | + } | ||
203 | + } | ||
204 | + | ||
205 | + mobileModeChanged() { | ||
206 | + const animation = this.ctx.settings.animation !== false && !this.ctx.isMobile; | ||
207 | + this.gauge.update({animation, isMobile: this.ctx.isMobile} as CanvasDigitalGaugeOptions); | ||
208 | + } | ||
209 | + | ||
210 | + resize() { | ||
211 | + this.gauge.update({width: this.ctx.width, height: this.ctx.height} as GenericOptions); | ||
212 | + } | ||
213 | + | ||
214 | +} |
@@ -21,11 +21,11 @@ export type FontWeight = 'normal' | 'bold' | 'bolder' | 'lighter' | @@ -21,11 +21,11 @@ export type FontWeight = 'normal' | 'bold' | 'bolder' | 'lighter' | ||
21 | | '600' | '700' | '800' | '900'; | 21 | | '600' | '700' | '800' | '900'; |
22 | 22 | ||
23 | export interface FontSettings { | 23 | export interface FontSettings { |
24 | - family: string; | ||
25 | - size: number; | ||
26 | - style: FontStyle; | ||
27 | - weight: FontWeight; | ||
28 | - color: string; | 24 | + family?: string; |
25 | + size?: number; | ||
26 | + style?: FontStyle; | ||
27 | + weight?: FontWeight; | ||
28 | + color?: string; | ||
29 | shadowColor?: string; | 29 | shadowColor?: string; |
30 | } | 30 | } |
31 | 31 | ||
@@ -36,3 +36,9 @@ export function getFontFamily(fontSettings: FontSettings): string { | @@ -36,3 +36,9 @@ export function getFontFamily(fontSettings: FontSettings): string { | ||
36 | } | 36 | } |
37 | return family; | 37 | return family; |
38 | } | 38 | } |
39 | + | ||
40 | +export function prepareFontSettings(fontSettings: FontSettings, defaultFontSettings: FontSettings): FontSettings { | ||
41 | + const result = {...defaultFontSettings, ...(fontSettings || {})}; | ||
42 | + result.family = getFontFamily(result); | ||
43 | + return result; | ||
44 | +} |
@@ -215,6 +215,7 @@ export class WidgetContext { | @@ -215,6 +215,7 @@ export class WidgetContext { | ||
215 | export interface IDynamicWidgetComponent { | 215 | export interface IDynamicWidgetComponent { |
216 | readonly ctx: WidgetContext; | 216 | readonly ctx: WidgetContext; |
217 | readonly errorMessages: string[]; | 217 | readonly errorMessages: string[]; |
218 | + readonly $injector: Injector; | ||
218 | executingRpcRequest: boolean; | 219 | executingRpcRequest: boolean; |
219 | rpcEnabled: boolean; | 220 | rpcEnabled: boolean; |
220 | rpcErrorText: string; | 221 | rpcErrorText: string; |
@@ -17,19 +17,19 @@ | @@ -17,19 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <mat-tab *ngIf="entity" | 18 | <mat-tab *ngIf="entity" |
19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> | 19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> |
20 | - <tb-attribute-table [active]="attributesTab.isActive" | 20 | + <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE" |
21 | + [active]="attributesTab.isActive" | ||
21 | [entityId]="entity.id" | 22 | [entityId]="entity.id" |
22 | - [entityName]="entity.name" | ||
23 | - [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> | 23 | + [entityName]="entity.name"> |
24 | </tb-attribute-table> | 24 | </tb-attribute-table> |
25 | </mat-tab> | 25 | </mat-tab> |
26 | <mat-tab *ngIf="entity" | 26 | <mat-tab *ngIf="entity" |
27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> | 27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> |
28 | - <tb-attribute-table [active]="telemetryTab.isActive" | 28 | + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" |
29 | + disableAttributeScopeSelection | ||
30 | + [active]="telemetryTab.isActive" | ||
29 | [entityId]="entity.id" | 31 | [entityId]="entity.id" |
30 | - [entityName]="entity.name" | ||
31 | - [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" | ||
32 | - disableAttributeScopeSelection> | 32 | + [entityName]="entity.name"> |
33 | </tb-attribute-table> | 33 | </tb-attribute-table> |
34 | </mat-tab> | 34 | </mat-tab> |
35 | <mat-tab *ngIf="entity" | 35 | <mat-tab *ngIf="entity" |
@@ -38,7 +38,7 @@ | @@ -38,7 +38,7 @@ | ||
38 | </mat-tab> | 38 | </mat-tab> |
39 | <mat-tab *ngIf="entity" | 39 | <mat-tab *ngIf="entity" |
40 | label="{{ 'asset.events' | translate }}" #eventsTab="matTab"> | 40 | label="{{ 'asset.events' | translate }}" #eventsTab="matTab"> |
41 | - <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" | 41 | + <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="entity.tenantId.id" |
42 | [entityId]="entity.id"></tb-event-table> | 42 | [entityId]="entity.id"></tb-event-table> |
43 | </mat-tab> | 43 | </mat-tab> |
44 | <mat-tab *ngIf="entity" | 44 | <mat-tab *ngIf="entity" |
@@ -47,5 +47,5 @@ | @@ -47,5 +47,5 @@ | ||
47 | </mat-tab> | 47 | </mat-tab> |
48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" | 48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> | 49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
50 | - <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> | 50 | + <tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table> |
51 | </mat-tab> | 51 | </mat-tab> |
@@ -17,19 +17,19 @@ | @@ -17,19 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <mat-tab *ngIf="entity" | 18 | <mat-tab *ngIf="entity" |
19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> | 19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> |
20 | - <tb-attribute-table [active]="attributesTab.isActive" | 20 | + <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE" |
21 | + [active]="attributesTab.isActive" | ||
21 | [entityId]="entity.id" | 22 | [entityId]="entity.id" |
22 | - [entityName]="entity.name" | ||
23 | - [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> | 23 | + [entityName]="entity.name"> |
24 | </tb-attribute-table> | 24 | </tb-attribute-table> |
25 | </mat-tab> | 25 | </mat-tab> |
26 | <mat-tab *ngIf="entity" | 26 | <mat-tab *ngIf="entity" |
27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> | 27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> |
28 | - <tb-attribute-table [active]="telemetryTab.isActive" | 28 | + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" |
29 | + disableAttributeScopeSelection | ||
30 | + [active]="telemetryTab.isActive" | ||
29 | [entityId]="entity.id" | 31 | [entityId]="entity.id" |
30 | - [entityName]="entity.name" | ||
31 | - [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" | ||
32 | - disableAttributeScopeSelection> | 32 | + [entityName]="entity.name"> |
33 | </tb-attribute-table> | 33 | </tb-attribute-table> |
34 | </mat-tab> | 34 | </mat-tab> |
35 | <mat-tab *ngIf="entity" | 35 | <mat-tab *ngIf="entity" |
@@ -38,7 +38,7 @@ | @@ -38,7 +38,7 @@ | ||
38 | </mat-tab> | 38 | </mat-tab> |
39 | <mat-tab *ngIf="entity" | 39 | <mat-tab *ngIf="entity" |
40 | label="{{ 'customer.events' | translate }}" #eventsTab="matTab"> | 40 | label="{{ 'customer.events' | translate }}" #eventsTab="matTab"> |
41 | - <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" | 41 | + <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="entity.tenantId.id" |
42 | [entityId]="entity.id"></tb-event-table> | 42 | [entityId]="entity.id"></tb-event-table> |
43 | </mat-tab> | 43 | </mat-tab> |
44 | <mat-tab *ngIf="entity" | 44 | <mat-tab *ngIf="entity" |
@@ -47,5 +47,5 @@ | @@ -47,5 +47,5 @@ | ||
47 | </mat-tab> | 47 | </mat-tab> |
48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" | 48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> | 49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
50 | - <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table> | 50 | + <tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id"></tb-audit-log-table> |
51 | </mat-tab> | 51 | </mat-tab> |
@@ -17,19 +17,19 @@ | @@ -17,19 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <mat-tab *ngIf="entity" | 18 | <mat-tab *ngIf="entity" |
19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> | 19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> |
20 | - <tb-attribute-table [active]="attributesTab.isActive" | 20 | + <tb-attribute-table [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE" |
21 | + [active]="attributesTab.isActive" | ||
21 | [entityId]="entity.id" | 22 | [entityId]="entity.id" |
22 | - [entityName]="entity.name" | ||
23 | - [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE"> | 23 | + [entityName]="entity.name"> |
24 | </tb-attribute-table> | 24 | </tb-attribute-table> |
25 | </mat-tab> | 25 | </mat-tab> |
26 | <mat-tab *ngIf="entity" | 26 | <mat-tab *ngIf="entity" |
27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> | 27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> |
28 | - <tb-attribute-table [active]="telemetryTab.isActive" | 28 | + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" |
29 | + disableAttributeScopeSelection | ||
30 | + [active]="telemetryTab.isActive" | ||
29 | [entityId]="entity.id" | 31 | [entityId]="entity.id" |
30 | - [entityName]="entity.name" | ||
31 | - [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" | ||
32 | - disableAttributeScopeSelection> | 32 | + [entityName]="entity.name"> |
33 | </tb-attribute-table> | 33 | </tb-attribute-table> |
34 | </mat-tab> | 34 | </mat-tab> |
35 | <mat-tab *ngIf="entity" | 35 | <mat-tab *ngIf="entity" |
@@ -38,7 +38,7 @@ | @@ -38,7 +38,7 @@ | ||
38 | </mat-tab> | 38 | </mat-tab> |
39 | <mat-tab *ngIf="entity" | 39 | <mat-tab *ngIf="entity" |
40 | label="{{ 'device.events' | translate }}" #eventsTab="matTab"> | 40 | label="{{ 'device.events' | translate }}" #eventsTab="matTab"> |
41 | - <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" | 41 | + <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="entity.tenantId.id" |
42 | [entityId]="entity.id"></tb-event-table> | 42 | [entityId]="entity.id"></tb-event-table> |
43 | </mat-tab> | 43 | </mat-tab> |
44 | <mat-tab *ngIf="entity" | 44 | <mat-tab *ngIf="entity" |
@@ -47,5 +47,5 @@ | @@ -47,5 +47,5 @@ | ||
47 | </mat-tab> | 47 | </mat-tab> |
48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" | 48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> | 49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
50 | - <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> | 50 | + <tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table> |
51 | </mat-tab> | 51 | </mat-tab> |
@@ -17,19 +17,19 @@ | @@ -17,19 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <mat-tab *ngIf="entity" | 18 | <mat-tab *ngIf="entity" |
19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> | 19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> |
20 | - <tb-attribute-table [active]="attributesTab.isActive" | 20 | + <tb-attribute-table [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE" |
21 | + [active]="attributesTab.isActive" | ||
21 | [entityId]="entity.id" | 22 | [entityId]="entity.id" |
22 | - [entityName]="entity.name" | ||
23 | - [defaultAttributeScope]="attributeScopes.CLIENT_SCOPE"> | 23 | + [entityName]="entity.name"> |
24 | </tb-attribute-table> | 24 | </tb-attribute-table> |
25 | </mat-tab> | 25 | </mat-tab> |
26 | <mat-tab *ngIf="entity" | 26 | <mat-tab *ngIf="entity" |
27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> | 27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> |
28 | - <tb-attribute-table [active]="telemetryTab.isActive" | 28 | + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" |
29 | + disableAttributeScopeSelection | ||
30 | + [active]="telemetryTab.isActive" | ||
29 | [entityId]="entity.id" | 31 | [entityId]="entity.id" |
30 | - [entityName]="entity.name" | ||
31 | - [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" | ||
32 | - disableAttributeScopeSelection> | 32 | + [entityName]="entity.name"> |
33 | </tb-attribute-table> | 33 | </tb-attribute-table> |
34 | </mat-tab> | 34 | </mat-tab> |
35 | <mat-tab *ngIf="entity" | 35 | <mat-tab *ngIf="entity" |
@@ -38,7 +38,7 @@ | @@ -38,7 +38,7 @@ | ||
38 | </mat-tab> | 38 | </mat-tab> |
39 | <mat-tab *ngIf="entity" | 39 | <mat-tab *ngIf="entity" |
40 | label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab"> | 40 | label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab"> |
41 | - <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" | 41 | + <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="entity.tenantId.id" |
42 | [entityId]="entity.id"></tb-event-table> | 42 | [entityId]="entity.id"></tb-event-table> |
43 | </mat-tab> | 43 | </mat-tab> |
44 | <mat-tab *ngIf="entity" | 44 | <mat-tab *ngIf="entity" |
@@ -47,5 +47,5 @@ | @@ -47,5 +47,5 @@ | ||
47 | </mat-tab> | 47 | </mat-tab> |
48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" | 48 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> | 49 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
50 | - <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> | 50 | + <tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table> |
51 | </mat-tab> | 51 | </mat-tab> |
@@ -121,9 +121,9 @@ | @@ -121,9 +121,9 @@ | ||
121 | </tb-rule-node> | 121 | </tb-rule-node> |
122 | </mat-tab> | 122 | </mat-tab> |
123 | <mat-tab *ngIf="editingRuleNode.ruleNodeId" label="{{ 'rulenode.events' | translate }}" #eventsTab="matTab"> | 123 | <mat-tab *ngIf="editingRuleNode.ruleNodeId" label="{{ 'rulenode.events' | translate }}" #eventsTab="matTab"> |
124 | - <tb-event-table [active]="eventsTab.isActive" | ||
125 | - [debugEventTypes]="[debugEventTypes.DEBUG_RULE_NODE]" | 124 | + <tb-event-table [debugEventTypes]="[debugEventTypes.DEBUG_RULE_NODE]" |
126 | [defaultEventType]="debugEventTypes.DEBUG_RULE_NODE" | 125 | [defaultEventType]="debugEventTypes.DEBUG_RULE_NODE" |
126 | + [active]="eventsTab.isActive" | ||
127 | [tenantId]="ruleChain.tenantId.id" | 127 | [tenantId]="ruleChain.tenantId.id" |
128 | [entityId]="editingRuleNode.ruleNodeId"></tb-event-table> | 128 | [entityId]="editingRuleNode.ruleNodeId"></tb-event-table> |
129 | </mat-tab> | 129 | </mat-tab> |
@@ -17,19 +17,19 @@ | @@ -17,19 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <mat-tab *ngIf="entity" | 18 | <mat-tab *ngIf="entity" |
19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> | 19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> |
20 | - <tb-attribute-table [active]="attributesTab.isActive" | 20 | + <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE" |
21 | + [active]="attributesTab.isActive" | ||
21 | [entityId]="entity.id" | 22 | [entityId]="entity.id" |
22 | - [entityName]="entity.name" | ||
23 | - [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> | 23 | + [entityName]="entity.name"> |
24 | </tb-attribute-table> | 24 | </tb-attribute-table> |
25 | </mat-tab> | 25 | </mat-tab> |
26 | <mat-tab *ngIf="entity" | 26 | <mat-tab *ngIf="entity" |
27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> | 27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> |
28 | - <tb-attribute-table [active]="telemetryTab.isActive" | 28 | + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" |
29 | + disableAttributeScopeSelection | ||
30 | + [active]="telemetryTab.isActive" | ||
29 | [entityId]="entity.id" | 31 | [entityId]="entity.id" |
30 | - [entityName]="entity.name" | ||
31 | - [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" | ||
32 | - disableAttributeScopeSelection> | 32 | + [entityName]="entity.name"> |
33 | </tb-attribute-table> | 33 | </tb-attribute-table> |
34 | </mat-tab> | 34 | </mat-tab> |
35 | <mat-tab *ngIf="entity" | 35 | <mat-tab *ngIf="entity" |
@@ -38,9 +38,9 @@ | @@ -38,9 +38,9 @@ | ||
38 | </mat-tab> | 38 | </mat-tab> |
39 | <mat-tab *ngIf="entity" | 39 | <mat-tab *ngIf="entity" |
40 | label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab"> | 40 | label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab"> |
41 | - <tb-event-table [active]="eventsTab.isActive" | ||
42 | - [debugEventTypes]="[debugEventTypes.DEBUG_RULE_CHAIN]" | 41 | + <tb-event-table [debugEventTypes]="[debugEventTypes.DEBUG_RULE_CHAIN]" |
43 | [defaultEventType]="debugEventTypes.DEBUG_RULE_CHAIN" | 42 | [defaultEventType]="debugEventTypes.DEBUG_RULE_CHAIN" |
43 | + [active]="eventsTab.isActive" | ||
44 | [tenantId]="entity.tenantId.id" | 44 | [tenantId]="entity.tenantId.id" |
45 | [entityId]="entity.id"></tb-event-table> | 45 | [entityId]="entity.id"></tb-event-table> |
46 | </mat-tab> | 46 | </mat-tab> |
@@ -50,5 +50,5 @@ | @@ -50,5 +50,5 @@ | ||
50 | </mat-tab> | 50 | </mat-tab> |
51 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" | 51 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
52 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> | 52 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
53 | - <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> | 53 | + <tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table> |
54 | </mat-tab> | 54 | </mat-tab> |
@@ -17,19 +17,19 @@ | @@ -17,19 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <mat-tab *ngIf="entity" | 18 | <mat-tab *ngIf="entity" |
19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> | 19 | label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> |
20 | - <tb-attribute-table [active]="attributesTab.isActive" | 20 | + <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE" |
21 | + [active]="attributesTab.isActive" | ||
21 | [entityId]="entity.id" | 22 | [entityId]="entity.id" |
22 | - [entityName]="entity.name" | ||
23 | - [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"> | 23 | + [entityName]="entity.name"> |
24 | </tb-attribute-table> | 24 | </tb-attribute-table> |
25 | </mat-tab> | 25 | </mat-tab> |
26 | <mat-tab *ngIf="entity" | 26 | <mat-tab *ngIf="entity" |
27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> | 27 | label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> |
28 | - <tb-attribute-table [active]="telemetryTab.isActive" | 28 | + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" |
29 | + disableAttributeScopeSelection | ||
30 | + [active]="telemetryTab.isActive" | ||
29 | [entityId]="entity.id" | 31 | [entityId]="entity.id" |
30 | - [entityName]="entity.name" | ||
31 | - [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" | ||
32 | - disableAttributeScopeSelection> | 32 | + [entityName]="entity.name"> |
33 | </tb-attribute-table> | 33 | </tb-attribute-table> |
34 | </mat-tab> | 34 | </mat-tab> |
35 | <mat-tab *ngIf="entity" | 35 | <mat-tab *ngIf="entity" |
@@ -38,7 +38,7 @@ | @@ -38,7 +38,7 @@ | ||
38 | </mat-tab> | 38 | </mat-tab> |
39 | <mat-tab *ngIf="entity" | 39 | <mat-tab *ngIf="entity" |
40 | label="{{ 'tenant.events' | translate }}" #eventsTab="matTab"> | 40 | label="{{ 'tenant.events' | translate }}" #eventsTab="matTab"> |
41 | - <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid" | 41 | + <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="nullUid" |
42 | [entityId]="entity.id"></tb-event-table> | 42 | [entityId]="entity.id"></tb-event-table> |
43 | </mat-tab> | 43 | </mat-tab> |
44 | <mat-tab *ngIf="entity" | 44 | <mat-tab *ngIf="entity" |
@@ -91,6 +91,7 @@ import { TbFlot } from '@home/components/widget/lib/flot-widget'; | @@ -91,6 +91,7 @@ import { TbFlot } from '@home/components/widget/lib/flot-widget'; | ||
91 | import { TbAnalogueCompass } from '@home/components/widget/lib/analogue-compass'; | 91 | import { TbAnalogueCompass } from '@home/components/widget/lib/analogue-compass'; |
92 | import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radial-gauge'; | 92 | import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radial-gauge'; |
93 | import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge'; | 93 | import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge'; |
94 | +import { TbCanvasDigitalGauge } from '@home/components/widget/lib/digital-gauge'; | ||
94 | 95 | ||
95 | import * as tinycolor_ from 'tinycolor2'; | 96 | import * as tinycolor_ from 'tinycolor2'; |
96 | 97 | ||
@@ -102,3 +103,4 @@ const tinycolor = tinycolor_; | @@ -102,3 +103,4 @@ const tinycolor = tinycolor_; | ||
102 | (window as any).TbAnalogueCompass = TbAnalogueCompass; | 103 | (window as any).TbAnalogueCompass = TbAnalogueCompass; |
103 | (window as any).TbAnalogueRadialGauge = TbAnalogueRadialGauge; | 104 | (window as any).TbAnalogueRadialGauge = TbAnalogueRadialGauge; |
104 | (window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge; | 105 | (window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge; |
106 | +(window as any).TbCanvasDigitalGauge = TbCanvasDigitalGauge; |
@@ -7,8 +7,15 @@ | @@ -7,8 +7,15 @@ | ||
7 | "angularCompilerOptions": { | 7 | "angularCompilerOptions": { |
8 | "fullTemplateTypeCheck": true | 8 | "fullTemplateTypeCheck": true |
9 | }, | 9 | }, |
10 | + "files": [ | ||
11 | + "main.ts", | ||
12 | + "polyfills.ts" | ||
13 | + ], | ||
10 | "exclude": [ | 14 | "exclude": [ |
11 | "test.ts", | 15 | "test.ts", |
12 | "**/*.spec.ts" | 16 | "**/*.spec.ts" |
17 | + ], | ||
18 | + "include": [ | ||
19 | + "**/*.d.ts" | ||
13 | ] | 20 | ] |
14 | } | 21 | } |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | + | ||
18 | +// tslint:disable-next-line:no-namespace | ||
19 | +declare namespace CanvasGauges { | ||
20 | + const drawings: Drawings; | ||
21 | +} | ||
22 | + | ||
23 | +interface Drawings { | ||
24 | + font(options: CanvasGauges.GenericOptions, target: string, baseSize: number): string; | ||
25 | + normalizedValue(options: CanvasGauges.GenericOptions): {normal: number, indented: number}; | ||
26 | + verifyError(err: any); | ||
27 | +} |
@@ -19,7 +19,8 @@ | @@ -19,7 +19,8 @@ | ||
19 | "src/typings/jquery.typings.d.ts", | 19 | "src/typings/jquery.typings.d.ts", |
20 | "src/typings/jquery.flot.typings.d.ts", | 20 | "src/typings/jquery.flot.typings.d.ts", |
21 | "src/typings/jquery.jstree.typings.d.ts", | 21 | "src/typings/jquery.jstree.typings.d.ts", |
22 | - "src/typings/split.js.typings.d.ts" | 22 | + "src/typings/split.js.typings.d.ts", |
23 | + "src/typings/canvas-gauges.typings.d.ts" | ||
23 | ], | 24 | ], |
24 | "paths": { | 25 | "paths": { |
25 | "@app/*": ["src/app/*"], | 26 | "@app/*": ["src/app/*"], |