Commit 9eec8c0b8c3451bdc47f7ae7f05bd0e7f6cf6dd5

Authored by Igor Kulikov
1 parent 8771a6f7

Implement Digital Gauges

@@ -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/*"],