Commit 8771a6f7540fce7d12e12485ec22d166c5ed455b

Authored by Igor Kulikov
1 parent 3378aa1d

Implement Analogue gauges

... ... @@ -3037,6 +3037,12 @@
3037 3037 }
3038 3038 }
3039 3039 },
  3040 + "@types/canvas-gauges": {
  3041 + "version": "2.1.2",
  3042 + "resolved": "https://registry.npmjs.org/@types/canvas-gauges/-/canvas-gauges-2.1.2.tgz",
  3043 + "integrity": "sha512-oWCq0XjsTBXPtMKXoW23ORbMWguC2Fa8o5NiZVYiUoQMMrpNLKj1E+LDznlMpcib3iyWVIy+TEpc/ea6LMbW3Q==",
  3044 + "dev": true
  3045 + },
3040 3046 "@types/estree": {
3041 3047 "version": "0.0.42",
3042 3048 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz",
... ... @@ -4435,6 +4441,11 @@
4435 4441 "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==",
4436 4442 "dev": true
4437 4443 },
  4444 + "canvas-gauges": {
  4445 + "version": "2.1.5",
  4446 + "resolved": "https://registry.npmjs.org/canvas-gauges/-/canvas-gauges-2.1.5.tgz",
  4447 + "integrity": "sha512-7GUd1uukePQPQPIoM8Sh4UrG8om+2RG+D8WN5BCkIp9wAfByzPuZZinsUkfFCyRrEOZ/rhuwBfFnb1ld8IfNrw=="
  4448 + },
4438 4449 "caseless": {
4439 4450 "version": "0.12.0",
4440 4451 "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
... ...
... ... @@ -42,6 +42,7 @@
42 42 "angular-gridster2": "^9.0.0",
43 43 "angular2-hotkeys": "^2.1.5",
44 44 "base64-js": "^1.3.1",
  45 + "canvas-gauges": "^2.1.5",
45 46 "compass-sass-mixins": "^0.12.7",
46 47 "core-js": "^3.6.4",
47 48 "date-fns": "^2.9.0",
... ... @@ -91,6 +92,7 @@
91 92 "@angular/cli": "^9.0.1",
92 93 "@angular/compiler-cli": "~9.0.0",
93 94 "@angular/language-service": "~9.0.0",
  95 + "@types/canvas-gauges": "^2.1.2",
94 96 "@types/flot": "0.0.31",
95 97 "@types/jasmine": "^3.5.3",
96 98 "@types/jasminewd2": "~2.0.8",
... ...
  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 { FontSettings } from '@home/components/widget/lib/settings.models';
  19 +import { AnimationRule, AnimationTarget } from '@home/components/widget/lib/analogue-gauge.models';
  20 +
  21 +export interface AnalogueCompassSettings {
  22 + majorTicks: string[];
  23 + minorTicks: number;
  24 + showStrokeTicks: boolean;
  25 + needleCircleSize: number;
  26 + showBorder: boolean;
  27 + borderOuterWidth: number;
  28 + colorPlate: string;
  29 + colorMajorTicks: string;
  30 + colorMinorTicks: string;
  31 + colorNeedle: string;
  32 + colorNeedleCircle: string;
  33 + colorBorder: string;
  34 + majorTickFont: FontSettings;
  35 + animation: boolean;
  36 + animationDuration: number;
  37 + animationRule: AnimationRule;
  38 + animationTarget: AnimationTarget;
  39 +}
  40 +
  41 +export const analogueCompassSettingsSchema: JsonSettingsSchema = {
  42 + schema: {
  43 + type: 'object',
  44 + title: 'Settings',
  45 + properties: {
  46 + majorTicks: {
  47 + title: 'Major ticks names',
  48 + type: 'array',
  49 + items: {
  50 + title: 'Tick name',
  51 + type: 'string'
  52 + }
  53 + },
  54 + minorTicks: {
  55 + title: 'Minor ticks count',
  56 + type: 'number',
  57 + default: 22
  58 + },
  59 + showStrokeTicks: {
  60 + title: 'Show ticks stroke',
  61 + type: 'boolean',
  62 + default: false
  63 + },
  64 + needleCircleSize: {
  65 + title: 'Needle circle size',
  66 + type: 'number',
  67 + default: 15
  68 + },
  69 + showBorder: {
  70 + title: 'Show border',
  71 + type: 'boolean',
  72 + default: true
  73 + },
  74 + borderOuterWidth: {
  75 + title: 'Border width',
  76 + type: 'number',
  77 + default: 10
  78 + },
  79 + colorPlate: {
  80 + title: 'Plate color',
  81 + type: 'string',
  82 + default: '#222'
  83 + },
  84 + colorMajorTicks: {
  85 + title: 'Major ticks color',
  86 + type: 'string',
  87 + default: '#f5f5f5'
  88 + },
  89 + colorMinorTicks: {
  90 + title: 'Minor ticks color',
  91 + type: 'string',
  92 + default: '#ddd'
  93 + },
  94 + colorNeedle: {
  95 + title: 'Needle color',
  96 + type: 'string',
  97 + default: '#f08080'
  98 + },
  99 + colorNeedleCircle: {
  100 + title: 'Needle circle color',
  101 + type: 'string',
  102 + default: '#e8e8e8'
  103 + },
  104 + colorBorder: {
  105 + title: 'Border color',
  106 + type: 'string',
  107 + default: '#ccc'
  108 + },
  109 + majorTickFont: {
  110 + title: 'Major tick font',
  111 + type: 'object',
  112 + properties: {
  113 + family: {
  114 + title: 'Font family',
  115 + type: 'string',
  116 + default: 'Roboto'
  117 + },
  118 + size: {
  119 + title: 'Size',
  120 + type: 'number',
  121 + default: 20
  122 + },
  123 + style: {
  124 + title: 'Style',
  125 + type: 'string',
  126 + default: 'normal'
  127 + },
  128 + weight: {
  129 + title: 'Weight',
  130 + type: 'string',
  131 + default: '500'
  132 + },
  133 + color: {
  134 + title: 'color',
  135 + type: 'string',
  136 + default: '#ccc'
  137 + }
  138 + }
  139 + },
  140 + animation: {
  141 + title: 'Enable animation',
  142 + type: 'boolean',
  143 + default: true
  144 + },
  145 + animationDuration: {
  146 + title: 'Animation duration',
  147 + type: 'number',
  148 + default: 500
  149 + },
  150 + animationRule: {
  151 + title: 'Animation rule',
  152 + type: 'string',
  153 + default: 'cycle'
  154 + },
  155 + animationTarget: {
  156 + title: 'Animation target',
  157 + type: 'string',
  158 + default: 'needle'
  159 + }
  160 + },
  161 + required: []
  162 + },
  163 + form: [
  164 + {
  165 + key: 'majorTicks',
  166 + items:[
  167 + 'majorTicks[]'
  168 + ]
  169 + },
  170 + 'minorTicks',
  171 + 'showStrokeTicks',
  172 + 'needleCircleSize',
  173 + 'showBorder',
  174 + 'borderOuterWidth',
  175 + {
  176 + key: 'colorPlate',
  177 + type: 'color'
  178 + },
  179 + {
  180 + key: 'colorMajorTicks',
  181 + type: 'color'
  182 + },
  183 + {
  184 + key: 'colorMinorTicks',
  185 + type: 'color'
  186 + },
  187 + {
  188 + key: 'colorNeedle',
  189 + type: 'color'
  190 + },
  191 + {
  192 + key: 'colorNeedleCircle',
  193 + type: 'color'
  194 + },
  195 + {
  196 + key: 'colorBorder',
  197 + type: 'color'
  198 + },
  199 + {
  200 + key: 'majorTickFont',
  201 + items: [
  202 + 'majorTickFont.family',
  203 + 'majorTickFont.size',
  204 + {
  205 + key: 'majorTickFont.style',
  206 + type: 'rc-select',
  207 + multiple: false,
  208 + items: [
  209 + {
  210 + value: 'normal',
  211 + label: 'Normal'
  212 + },
  213 + {
  214 + value: 'italic',
  215 + label: 'Italic'
  216 + },
  217 + {
  218 + value: 'oblique',
  219 + label: 'Oblique'
  220 + }
  221 + ]
  222 + },
  223 + {
  224 + key: 'majorTickFont.weight',
  225 + type: 'rc-select',
  226 + multiple: false,
  227 + items: [
  228 + {
  229 + value: 'normal',
  230 + label: 'Normal'
  231 + },
  232 + {
  233 + value: 'bold',
  234 + label: 'Bold'
  235 + },
  236 + {
  237 + value: 'bolder',
  238 + label: 'Bolder'
  239 + },
  240 + {
  241 + value: 'lighter',
  242 + label: 'Lighter'
  243 + },
  244 + {
  245 + value: '100',
  246 + label: '100'
  247 + },
  248 + {
  249 + value: '200',
  250 + label: '200'
  251 + },
  252 + {
  253 + value: '300',
  254 + label: '300'
  255 + },
  256 + {
  257 + value: '400',
  258 + label: '400'
  259 + },
  260 + {
  261 + value: '500',
  262 + label: '500'
  263 + },
  264 + {
  265 + value: '600',
  266 + label: '600'
  267 + },
  268 + {
  269 + value: '700',
  270 + label: '800'
  271 + },
  272 + {
  273 + value: '800',
  274 + label: '800'
  275 + },
  276 + {
  277 + value: '900',
  278 + label: '900'
  279 + }
  280 + ]
  281 + },
  282 + {
  283 + key: 'majorTickFont.color',
  284 + type: 'color'
  285 + }
  286 + ]
  287 + },
  288 + 'animation',
  289 + 'animationDuration',
  290 + {
  291 + key: 'animationRule',
  292 + type: 'rc-select',
  293 + multiple: false,
  294 + items: [
  295 + {
  296 + value: 'linear',
  297 + label: 'Linear'
  298 + },
  299 + {
  300 + value: 'quad',
  301 + label: 'Quad'
  302 + },
  303 + {
  304 + value: 'quint',
  305 + label: 'Quint'
  306 + },
  307 + {
  308 + value: 'cycle',
  309 + label: 'Cycle'
  310 + },
  311 + {
  312 + value: 'bounce',
  313 + label: 'Bounce'
  314 + },
  315 + {
  316 + value: 'elastic',
  317 + label: 'Elastic'
  318 + },
  319 + {
  320 + value: 'dequad',
  321 + label: 'Dequad'
  322 + },
  323 + {
  324 + value: 'dequint',
  325 + label: 'Dequint'
  326 + },
  327 + {
  328 + value: 'decycle',
  329 + label: 'Decycle'
  330 + },
  331 + {
  332 + value: 'debounce',
  333 + label: 'Debounce'
  334 + },
  335 + {
  336 + value: 'delastic',
  337 + label: 'Delastic'
  338 + }
  339 + ]
  340 + },
  341 + {
  342 + key: 'animationTarget',
  343 + type: 'rc-select',
  344 + multiple: false,
  345 + items: [
  346 + {
  347 + value: 'needle',
  348 + label: 'Needle'
  349 + },
  350 + {
  351 + value: 'plate',
  352 + label: 'Plate'
  353 + }
  354 + ]
  355 + }
  356 + ]
  357 +};
... ...
  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 { WidgetContext } from '@home/models/widget-component.models';
  18 +import * as CanvasGauges from 'canvas-gauges';
  19 +import {
  20 + AnalogueCompassSettings,
  21 + analogueCompassSettingsSchema
  22 +} from '@home/components/widget/lib/analogue-compass.models';
  23 +import { deepClone, isDefined } from '@core/utils';
  24 +import { JsonSettingsSchema } from '@shared/models/widget.models';
  25 +import { getFontFamily } from '@home/components/widget/lib/settings.models';
  26 +import { TbBaseGauge } from '@home/components/widget/lib/analogue-gauge.models';
  27 +import RadialGaugeOptions = CanvasGauges.RadialGaugeOptions;
  28 +import BaseGauge = CanvasGauges.BaseGauge;
  29 +import RadialGauge = CanvasGauges.RadialGauge;
  30 +
  31 +const analogueCompassSettingsSchemaValue = analogueCompassSettingsSchema;
  32 +
  33 +export class TbAnalogueCompass extends TbBaseGauge<AnalogueCompassSettings, RadialGaugeOptions> {
  34 +
  35 + static get settingsSchema(): JsonSettingsSchema {
  36 + return analogueCompassSettingsSchemaValue;
  37 + }
  38 +
  39 + constructor(ctx: WidgetContext, canvasId: string) {
  40 + super(ctx, canvasId);
  41 + }
  42 +
  43 + protected createGaugeOptions(gaugeElement: HTMLElement, settings: AnalogueCompassSettings): RadialGaugeOptions {
  44 +
  45 + const majorTicks = (settings.majorTicks && settings.majorTicks.length > 0) ? deepClone(settings.majorTicks) :
  46 + ['N','NE','E','SE','S','SW','W','NW'];
  47 + majorTicks.push(majorTicks[0]);
  48 +
  49 + return {
  50 + renderTo: gaugeElement,
  51 +
  52 + // Generic options
  53 + minValue: 0,
  54 + maxValue: 360,
  55 + majorTicks,
  56 + minorTicks: settings.minorTicks || 22,
  57 + ticksAngle: 360,
  58 + startAngle: 180,
  59 + strokeTicks: settings.showStrokeTicks || false,
  60 + highlights: [],
  61 + valueBox: false,
  62 +
  63 + // needle
  64 + needleCircleSize: settings.needleCircleSize || 15,
  65 + needleType: 'line',
  66 + needleStart: 75,
  67 + needleEnd: 99,
  68 + needleWidth: 3,
  69 + needleCircleOuter: false,
  70 +
  71 + // borders
  72 + borders: settings.showBorder || false,
  73 + borderInnerWidth: 0,
  74 + borderMiddleWidth: 0,
  75 + borderOuterWidth: settings.borderOuterWidth || 10,
  76 + borderShadowWidth: 0,
  77 +
  78 + // colors
  79 + colorPlate: settings.colorPlate || '#222',
  80 + colorMajorTicks: settings.colorMajorTicks || '#f5f5f5',
  81 + colorMinorTicks: settings.colorMinorTicks || '#ddd',
  82 + colorNeedle: settings.colorNeedle || '#f08080',
  83 + colorNeedleEnd: settings.colorNeedle || '#f08080',
  84 + colorNeedleCircleInner: settings.colorNeedleCircle || '#e8e8e8',
  85 + colorNeedleCircleInnerEnd: settings.colorNeedleCircle || '#e8e8e8',
  86 + colorBorderOuter: settings.colorBorder || '#ccc',
  87 + colorBorderOuterEnd: settings.colorBorder || '#ccc',
  88 + colorNeedleShadowDown: '#222',
  89 +
  90 + // fonts
  91 + fontNumbers: getFontFamily(settings.majorTickFont),
  92 + fontNumbersSize: settings.majorTickFont && settings.majorTickFont.size ? settings.majorTickFont.size : 20,
  93 + fontNumbersStyle: settings.majorTickFont && settings.majorTickFont.style ? settings.majorTickFont.style : 'normal',
  94 + fontNumbersWeight: settings.majorTickFont && settings.majorTickFont.weight ? settings.majorTickFont.weight : '500',
  95 + colorNumbers: settings.majorTickFont && settings.majorTickFont.color ? settings.majorTickFont.color : '#ccc',
  96 +
  97 + // animations
  98 + animation: settings.animation !== false && !this.ctx.isMobile,
  99 + animationDuration: (isDefined(settings.animationDuration) && settings.animationDuration !== null) ? settings.animationDuration : 500,
  100 + animationRule: settings.animationRule || 'cycle',
  101 + animationTarget: settings.animationTarget || 'needle'
  102 + };
  103 + }
  104 +
  105 + protected createGauge(gaugeData: RadialGaugeOptions): BaseGauge {
  106 + return new RadialGauge(gaugeData);
  107 + }
  108 +}
... ...
  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 +import { FontSettings, getFontFamily } from '@home/components/widget/lib/settings.models';
  19 +import { JsonSettingsSchema } from '@shared/models/widget.models';
  20 +import { WidgetContext } from '@home/models/widget-component.models';
  21 +import { isDefined } from '@core/utils';
  22 +import * as tinycolor_ from 'tinycolor2';
  23 +import Highlight = CanvasGauges.Highlight;
  24 +import BaseGauge = CanvasGauges.BaseGauge;
  25 +import GenericOptions = CanvasGauges.GenericOptions;
  26 +
  27 +const tinycolor = tinycolor_;
  28 +
  29 +export type AnimationRule = 'linear' | 'quad' | 'quint' | 'cycle'
  30 + | 'bounce' | 'elastic' | 'dequad' | 'dequint'
  31 + | 'decycle' | 'debounce' | 'delastic';
  32 +
  33 +export type AnimationTarget = 'needle' | 'plate';
  34 +
  35 +export interface AnalogueGaugeSettings {
  36 + minValue: number;
  37 + maxValue: number;
  38 + unitTitle: string;
  39 + showUnitTitle: boolean;
  40 + majorTicksCount: number;
  41 + minorTicks: number;
  42 + valueBox: boolean;
  43 + valueInt: number;
  44 + valueDec?: number;
  45 + units?: string;
  46 + defaultColor: string;
  47 + colorPlate: string;
  48 + colorMajorTicks: string;
  49 + colorMinorTicks: string;
  50 + colorNeedle: string;
  51 + colorNeedleEnd: string;
  52 + colorNeedleShadowUp: string;
  53 + colorNeedleShadowDown: string;
  54 + colorValueBoxRect: string;
  55 + colorValueBoxRectEnd: string;
  56 + colorValueBoxBackground: string;
  57 + colorValueBoxShadow: string;
  58 + highlights: Highlight[];
  59 + highlightsWidth: number;
  60 + showBorder: boolean;
  61 + numbersFont: FontSettings;
  62 + titleFont: FontSettings;
  63 + unitsFont: FontSettings;
  64 + valueFont: FontSettings;
  65 + animation: boolean;
  66 + animationDuration: number;
  67 + animationRule: AnimationRule;
  68 +}
  69 +
  70 +export const analogueGaugeSettingsSchema: JsonSettingsSchema = {
  71 + schema: {
  72 + type: 'object',
  73 + title: 'Settings',
  74 + properties: {
  75 + minValue: {
  76 + title: 'Minimum value',
  77 + type: 'number',
  78 + default: 0
  79 + },
  80 + maxValue: {
  81 + title: 'Maximum value',
  82 + type: 'number',
  83 + default: 100
  84 + },
  85 + unitTitle: {
  86 + title: 'Unit title',
  87 + type: 'string',
  88 + default: null
  89 + },
  90 + showUnitTitle: {
  91 + title: 'Show unit title',
  92 + type: 'boolean',
  93 + default: true
  94 + },
  95 + majorTicksCount: {
  96 + title: 'Major ticks count',
  97 + type: 'number',
  98 + default: null
  99 + },
  100 + minorTicks: {
  101 + title: 'Minor ticks count',
  102 + type: 'number',
  103 + default: 2
  104 + },
  105 + valueBox: {
  106 + title: 'Show value box',
  107 + type: 'boolean',
  108 + default: true
  109 + },
  110 + valueInt: {
  111 + title: 'Digits count for integer part of value',
  112 + type: 'number',
  113 + default: 3
  114 + },
  115 + defaultColor: {
  116 + title: 'Default color',
  117 + type: 'string',
  118 + default: null
  119 + },
  120 + colorPlate: {
  121 + title: 'Plate color',
  122 + type: 'string',
  123 + default: '#fff'
  124 + },
  125 + colorMajorTicks: {
  126 + title: 'Major ticks color',
  127 + type: 'string',
  128 + default: '#444'
  129 + },
  130 + colorMinorTicks: {
  131 + title: 'Minor ticks color',
  132 + type: 'string',
  133 + default: '#666'
  134 + },
  135 + colorNeedle: {
  136 + title: 'Needle color',
  137 + type: 'string',
  138 + default: null
  139 + },
  140 + colorNeedleEnd: {
  141 + title: 'Needle color - end gradient',
  142 + type: 'string',
  143 + default: null
  144 + },
  145 + colorNeedleShadowUp: {
  146 + title: 'Upper half of the needle shadow color',
  147 + type: 'string',
  148 + default: 'rgba(2,255,255,0.2)'
  149 + },
  150 + colorNeedleShadowDown: {
  151 + title: 'Drop shadow needle color.',
  152 + type: 'string',
  153 + default: 'rgba(188,143,143,0.45)'
  154 + },
  155 + colorValueBoxRect: {
  156 + title: 'Value box rectangle stroke color',
  157 + type: 'string',
  158 + default: '#888'
  159 + },
  160 + colorValueBoxRectEnd: {
  161 + title: 'Value box rectangle stroke color - end gradient',
  162 + type: 'string',
  163 + default: '#666'
  164 + },
  165 + colorValueBoxBackground: {
  166 + title: 'Value box background color',
  167 + type: 'string',
  168 + default: '#babab2'
  169 + },
  170 + colorValueBoxShadow: {
  171 + title: 'Value box shadow color',
  172 + type: 'string',
  173 + default: 'rgba(0,0,0,1)'
  174 + },
  175 + highlights: {
  176 + title: 'Highlights',
  177 + type: 'array',
  178 + items: {
  179 + title: 'Highlight',
  180 + type: 'object',
  181 + properties: {
  182 + from: {
  183 + title: 'From',
  184 + type: 'number'
  185 + },
  186 + to: {
  187 + title: 'To',
  188 + type: 'number'
  189 + },
  190 + color: {
  191 + title: 'Color',
  192 + type: 'string'
  193 + }
  194 + }
  195 + }
  196 + },
  197 + highlightsWidth: {
  198 + title: 'Highlights width',
  199 + type: 'number',
  200 + default: 15
  201 + },
  202 + showBorder: {
  203 + title: 'Show border',
  204 + type: 'boolean',
  205 + default: true
  206 + },
  207 + numbersFont: {
  208 + title: 'Tick numbers font',
  209 + type: 'object',
  210 + properties: {
  211 + family: {
  212 + title: 'Font family',
  213 + type: 'string',
  214 + default: 'Roboto'
  215 + },
  216 + size: {
  217 + title: 'Size',
  218 + type: 'number',
  219 + default: 18
  220 + },
  221 + style: {
  222 + title: 'Style',
  223 + type: 'string',
  224 + default: 'normal'
  225 + },
  226 + weight: {
  227 + title: 'Weight',
  228 + type: 'string',
  229 + default: '500'
  230 + },
  231 + color: {
  232 + title: 'color',
  233 + type: 'string',
  234 + default: null
  235 + }
  236 + }
  237 + },
  238 + titleFont: {
  239 + title: 'Title text font',
  240 + type: 'object',
  241 + properties: {
  242 + family: {
  243 + title: 'Font family',
  244 + type: 'string',
  245 + default: 'Roboto'
  246 + },
  247 + size: {
  248 + title: 'Size',
  249 + type: 'number',
  250 + default: 24
  251 + },
  252 + style: {
  253 + title: 'Style',
  254 + type: 'string',
  255 + default: 'normal'
  256 + },
  257 + weight: {
  258 + title: 'Weight',
  259 + type: 'string',
  260 + default: '500'
  261 + },
  262 + color: {
  263 + title: 'color',
  264 + type: 'string',
  265 + default: '#888'
  266 + }
  267 + }
  268 + },
  269 + unitsFont: {
  270 + title: 'Units text font',
  271 + type: 'object',
  272 + properties: {
  273 + family: {
  274 + title: 'Font family',
  275 + type: 'string',
  276 + default: 'Roboto'
  277 + },
  278 + size: {
  279 + title: 'Size',
  280 + type: 'number',
  281 + default: 22
  282 + },
  283 + style: {
  284 + title: 'Style',
  285 + type: 'string',
  286 + default: 'normal'
  287 + },
  288 + weight: {
  289 + title: 'Weight',
  290 + type: 'string',
  291 + default: '500'
  292 + },
  293 + color: {
  294 + title: 'color',
  295 + type: 'string',
  296 + default: '#888'
  297 + }
  298 + }
  299 + },
  300 + valueFont: {
  301 + title: 'Value text font',
  302 + type: 'object',
  303 + properties: {
  304 + family: {
  305 + title: 'Font family',
  306 + type: 'string',
  307 + default: 'Roboto'
  308 + },
  309 + size: {
  310 + title: 'Size',
  311 + type: 'number',
  312 + default: 40
  313 + },
  314 + style: {
  315 + title: 'Style',
  316 + type: 'string',
  317 + default: 'normal'
  318 + },
  319 + weight: {
  320 + title: 'Weight',
  321 + type: 'string',
  322 + default: '500'
  323 + },
  324 + color: {
  325 + title: 'color',
  326 + type: 'string',
  327 + default: '#444'
  328 + },
  329 + shadowColor: {
  330 + title: 'Shadow color',
  331 + type: 'string',
  332 + default: 'rgba(0,0,0,0.3)'
  333 + }
  334 + }
  335 + },
  336 + animation: {
  337 + title: 'Enable animation',
  338 + type: 'boolean',
  339 + default: true
  340 + },
  341 + animationDuration: {
  342 + title: 'Animation duration',
  343 + type: 'number',
  344 + default: 500
  345 + },
  346 + animationRule: {
  347 + title: 'Animation rule',
  348 + type: 'string',
  349 + default: 'cycle'
  350 + }
  351 + },
  352 + required: []
  353 + },
  354 + form: [
  355 + 'minValue',
  356 + 'maxValue',
  357 + 'unitTitle',
  358 + 'showUnitTitle',
  359 + 'majorTicksCount',
  360 + 'minorTicks',
  361 + 'valueBox',
  362 + 'valueInt',
  363 + {
  364 + key: 'defaultColor',
  365 + type: 'color'
  366 + },
  367 + {
  368 + key: 'colorPlate',
  369 + type: 'color'
  370 + },
  371 + {
  372 + key: 'colorMajorTicks',
  373 + type: 'color'
  374 + },
  375 + {
  376 + key: 'colorMinorTicks',
  377 + type: 'color'
  378 + },
  379 + {
  380 + key: 'colorNeedle',
  381 + type: 'color'
  382 + },
  383 + {
  384 + key: 'colorNeedleEnd',
  385 + type: 'color'
  386 + },
  387 + {
  388 + key: 'colorNeedleShadowUp',
  389 + type: 'color'
  390 + },
  391 + {
  392 + key: 'colorNeedleShadowDown',
  393 + type: 'color'
  394 + },
  395 + {
  396 + key: 'colorValueBoxRect',
  397 + type: 'color'
  398 + },
  399 + {
  400 + key: 'colorValueBoxRectEnd',
  401 + type: 'color'
  402 + },
  403 + {
  404 + key: 'colorValueBoxBackground',
  405 + type: 'color'
  406 + },
  407 + {
  408 + key: 'colorValueBoxShadow',
  409 + type: 'color'
  410 + },
  411 + {
  412 + key: 'highlights',
  413 + items: [
  414 + 'highlights[].from',
  415 + 'highlights[].to',
  416 + {
  417 + key: 'highlights[].color',
  418 + type: 'color'
  419 + }
  420 + ]
  421 + },
  422 + 'highlightsWidth',
  423 + 'showBorder',
  424 + {
  425 + key: 'numbersFont',
  426 + items: [
  427 + 'numbersFont.family',
  428 + 'numbersFont.size',
  429 + {
  430 + key: 'numbersFont.style',
  431 + type: 'rc-select',
  432 + multiple: false,
  433 + items: [
  434 + {
  435 + value: 'normal',
  436 + label: 'Normal'
  437 + },
  438 + {
  439 + value: 'italic',
  440 + label: 'Italic'
  441 + },
  442 + {
  443 + value: 'oblique',
  444 + label: 'Oblique'
  445 + }
  446 + ]
  447 + },
  448 + {
  449 + key: 'numbersFont.weight',
  450 + type: 'rc-select',
  451 + multiple: false,
  452 + items: [
  453 + {
  454 + value: 'normal',
  455 + label: 'Normal'
  456 + },
  457 + {
  458 + value: 'bold',
  459 + label: 'Bold'
  460 + },
  461 + {
  462 + value: 'bolder',
  463 + label: 'Bolder'
  464 + },
  465 + {
  466 + value: 'lighter',
  467 + label: 'Lighter'
  468 + },
  469 + {
  470 + value: '100',
  471 + label: '100'
  472 + },
  473 + {
  474 + value: '200',
  475 + label: '200'
  476 + },
  477 + {
  478 + value: '300',
  479 + label: '300'
  480 + },
  481 + {
  482 + value: '400',
  483 + label: '400'
  484 + },
  485 + {
  486 + value: '500',
  487 + label: '500'
  488 + },
  489 + {
  490 + value: '600',
  491 + label: '600'
  492 + },
  493 + {
  494 + value: '700',
  495 + label: '800'
  496 + },
  497 + {
  498 + value: '800',
  499 + label: '800'
  500 + },
  501 + {
  502 + value: '900',
  503 + label: '900'
  504 + }
  505 + ]
  506 + },
  507 + {
  508 + key: 'numbersFont.color',
  509 + type: 'color'
  510 + }
  511 + ]
  512 + },
  513 + {
  514 + key: 'titleFont',
  515 + items: [
  516 + 'titleFont.family',
  517 + 'titleFont.size',
  518 + {
  519 + key: 'titleFont.style',
  520 + type: 'rc-select',
  521 + multiple: false,
  522 + items: [
  523 + {
  524 + value: 'normal',
  525 + label: 'Normal'
  526 + },
  527 + {
  528 + value: 'italic',
  529 + label: 'Italic'
  530 + },
  531 + {
  532 + value: 'oblique',
  533 + label: 'Oblique'
  534 + }
  535 + ]
  536 + },
  537 + {
  538 + key: 'titleFont.weight',
  539 + type: 'rc-select',
  540 + multiple: false,
  541 + items: [
  542 + {
  543 + value: 'normal',
  544 + label: 'Normal'
  545 + },
  546 + {
  547 + value: 'bold',
  548 + label: 'Bold'
  549 + },
  550 + {
  551 + value: 'bolder',
  552 + label: 'Bolder'
  553 + },
  554 + {
  555 + value: 'lighter',
  556 + label: 'Lighter'
  557 + },
  558 + {
  559 + value: '100',
  560 + label: '100'
  561 + },
  562 + {
  563 + value: '200',
  564 + label: '200'
  565 + },
  566 + {
  567 + value: '300',
  568 + label: '300'
  569 + },
  570 + {
  571 + value: '400',
  572 + label: '400'
  573 + },
  574 + {
  575 + value: '500',
  576 + label: '500'
  577 + },
  578 + {
  579 + value: '600',
  580 + label: '600'
  581 + },
  582 + {
  583 + value: '700',
  584 + label: '800'
  585 + },
  586 + {
  587 + value: '800',
  588 + label: '800'
  589 + },
  590 + {
  591 + value: '900',
  592 + label: '900'
  593 + }
  594 + ]
  595 + },
  596 + {
  597 + key: 'titleFont.color',
  598 + type: 'color'
  599 + }
  600 + ]
  601 + },
  602 + {
  603 + key: 'unitsFont',
  604 + items: [
  605 + 'unitsFont.family',
  606 + 'unitsFont.size',
  607 + {
  608 + key: 'unitsFont.style',
  609 + type: 'rc-select',
  610 + multiple: false,
  611 + items: [
  612 + {
  613 + value: 'normal',
  614 + label: 'Normal'
  615 + },
  616 + {
  617 + value: 'italic',
  618 + label: 'Italic'
  619 + },
  620 + {
  621 + value: 'oblique',
  622 + label: 'Oblique'
  623 + }
  624 + ]
  625 + },
  626 + {
  627 + key: 'unitsFont.weight',
  628 + type: 'rc-select',
  629 + multiple: false,
  630 + items: [
  631 + {
  632 + value: 'normal',
  633 + label: 'Normal'
  634 + },
  635 + {
  636 + value: 'bold',
  637 + label: 'Bold'
  638 + },
  639 + {
  640 + value: 'bolder',
  641 + label: 'Bolder'
  642 + },
  643 + {
  644 + value: 'lighter',
  645 + label: 'Lighter'
  646 + },
  647 + {
  648 + value: '100',
  649 + label: '100'
  650 + },
  651 + {
  652 + value: '200',
  653 + label: '200'
  654 + },
  655 + {
  656 + value: '300',
  657 + label: '300'
  658 + },
  659 + {
  660 + value: '400',
  661 + label: '400'
  662 + },
  663 + {
  664 + value: '500',
  665 + label: '500'
  666 + },
  667 + {
  668 + value: '600',
  669 + label: '600'
  670 + },
  671 + {
  672 + value: '700',
  673 + label: '800'
  674 + },
  675 + {
  676 + value: '800',
  677 + label: '800'
  678 + },
  679 + {
  680 + value: '900',
  681 + label: '900'
  682 + }
  683 + ]
  684 + },
  685 + {
  686 + key: 'unitsFont.color',
  687 + type: 'color'
  688 + }
  689 + ]
  690 + },
  691 + {
  692 + key: 'valueFont',
  693 + items: [
  694 + 'valueFont.family',
  695 + 'valueFont.size',
  696 + {
  697 + key: 'valueFont.style',
  698 + type: 'rc-select',
  699 + multiple: false,
  700 + items: [
  701 + {
  702 + value: 'normal',
  703 + label: 'Normal'
  704 + },
  705 + {
  706 + value: 'italic',
  707 + label: 'Italic'
  708 + },
  709 + {
  710 + value: 'oblique',
  711 + label: 'Oblique'
  712 + }
  713 + ]
  714 + },
  715 + {
  716 + key: 'valueFont.weight',
  717 + type: 'rc-select',
  718 + multiple: false,
  719 + items: [
  720 + {
  721 + value: 'normal',
  722 + label: 'Normal'
  723 + },
  724 + {
  725 + value: 'bold',
  726 + label: 'Bold'
  727 + },
  728 + {
  729 + value: 'bolder',
  730 + label: 'Bolder'
  731 + },
  732 + {
  733 + value: 'lighter',
  734 + label: 'Lighter'
  735 + },
  736 + {
  737 + value: '100',
  738 + label: '100'
  739 + },
  740 + {
  741 + value: '200',
  742 + label: '200'
  743 + },
  744 + {
  745 + value: '300',
  746 + label: '300'
  747 + },
  748 + {
  749 + value: '400',
  750 + label: '400'
  751 + },
  752 + {
  753 + value: '500',
  754 + label: '500'
  755 + },
  756 + {
  757 + value: '600',
  758 + label: '600'
  759 + },
  760 + {
  761 + value: '700',
  762 + label: '800'
  763 + },
  764 + {
  765 + value: '800',
  766 + label: '800'
  767 + },
  768 + {
  769 + value: '900',
  770 + label: '900'
  771 + }
  772 + ]
  773 + },
  774 + {
  775 + key: 'valueFont.color',
  776 + type: 'color'
  777 + },
  778 + {
  779 + key: 'valueFont.shadowColor',
  780 + type: 'color'
  781 + }
  782 + ]
  783 + },
  784 + 'animation',
  785 + 'animationDuration',
  786 + {
  787 + key: 'animationRule',
  788 + type: 'rc-select',
  789 + multiple: false,
  790 + items: [
  791 + {
  792 + value: 'linear',
  793 + label: 'Linear'
  794 + },
  795 + {
  796 + value: 'quad',
  797 + label: 'Quad'
  798 + },
  799 + {
  800 + value: 'quint',
  801 + label: 'Quint'
  802 + },
  803 + {
  804 + value: 'cycle',
  805 + label: 'Cycle'
  806 + },
  807 + {
  808 + value: 'bounce',
  809 + label: 'Bounce'
  810 + },
  811 + {
  812 + value: 'elastic',
  813 + label: 'Elastic'
  814 + },
  815 + {
  816 + value: 'dequad',
  817 + label: 'Dequad'
  818 + },
  819 + {
  820 + value: 'dequint',
  821 + label: 'Dequint'
  822 + },
  823 + {
  824 + value: 'decycle',
  825 + label: 'Decycle'
  826 + },
  827 + {
  828 + value: 'debounce',
  829 + label: 'Debounce'
  830 + },
  831 + {
  832 + value: 'delastic',
  833 + label: 'Delastic'
  834 + }
  835 + ]
  836 + }
  837 + ]
  838 +};
  839 +
  840 +export abstract class TbBaseGauge<S, O extends GenericOptions> {
  841 +
  842 + private gauge: BaseGauge;
  843 +
  844 + protected constructor(protected ctx: WidgetContext, canvasId: string) {
  845 + const gaugeElement = $('#'+canvasId, ctx.$container)[0];
  846 + const settings: S = ctx.settings;
  847 + const gaugeData: O = this.createGaugeOptions(gaugeElement, settings);
  848 + this.gauge = this.createGauge(gaugeData as O).draw();
  849 + }
  850 +
  851 + protected abstract createGaugeOptions(gaugeElement: HTMLElement, settings: S): O;
  852 +
  853 + protected abstract createGauge(gaugeData: O): BaseGauge;
  854 +
  855 + update() {
  856 + if (this.ctx.data.length > 0) {
  857 + const cellData = this.ctx.data[0];
  858 + if (cellData.data.length > 0) {
  859 + const tvPair = cellData.data[cellData.data.length -
  860 + 1];
  861 + const value = tvPair[1];
  862 + if(value !== this.gauge.value) {
  863 + this.gauge.value = value;
  864 + }
  865 + }
  866 + }
  867 + }
  868 +
  869 + mobileModeChanged() {
  870 + const animation = this.ctx.settings.animation !== false && !this.ctx.isMobile;
  871 + this.gauge.update({animation} as GenericOptions);
  872 + }
  873 +
  874 + resize() {
  875 + this.gauge.update({width: this.ctx.width, height: this.ctx.height} as GenericOptions);
  876 + }
  877 +}
  878 +
  879 +export abstract class TbAnalogueGauge<S extends AnalogueGaugeSettings, O extends GenericOptions> extends TbBaseGauge<S,O> {
  880 +
  881 + protected constructor(ctx: WidgetContext, canvasId: string) {
  882 + super(ctx,canvasId);
  883 + }
  884 +
  885 + protected createGaugeOptions(gaugeElement: HTMLElement, settings: S): O {
  886 +
  887 + const minValue = settings.minValue || 0;
  888 + const maxValue = settings.maxValue || 100;
  889 +
  890 + const dataKey = this.ctx.data[0].dataKey;
  891 + const keyColor = settings.defaultColor || dataKey.color;
  892 +
  893 + const majorTicksCount = settings.majorTicksCount || 10;
  894 + const total = maxValue-minValue;
  895 + let step = (total/majorTicksCount);
  896 +
  897 + const valueInt = settings.valueInt || 3;
  898 +
  899 + const valueDec = getValueDec(this.ctx, settings);
  900 +
  901 + step = parseFloat(parseFloat(step+'').toFixed(valueDec));
  902 +
  903 + const majorTicks: number[] = [];
  904 + const highlights: Highlight[] = [];
  905 + let tick = minValue;
  906 +
  907 + while(tick<=maxValue) {
  908 + majorTicks.push(tick);
  909 + let nextTick = tick+step;
  910 + nextTick = parseFloat(parseFloat(nextTick+'').toFixed(valueDec));
  911 + if (tick<maxValue) {
  912 + const highlightColor = tinycolor(keyColor);
  913 + const percent = (tick-minValue)/total;
  914 + highlightColor.setAlpha(percent);
  915 + const highlight: Highlight = {
  916 + from: tick,
  917 + to: nextTick,
  918 + color: highlightColor.toRgbString()
  919 + };
  920 + highlights.push(highlight);
  921 + }
  922 + tick = nextTick;
  923 + }
  924 +
  925 + const colorNumbers = tinycolor(keyColor).darken(20).toRgbString();
  926 +
  927 + const gaugeData: O = {
  928 + renderTo: gaugeElement,
  929 +
  930 + /* Generic options */
  931 +
  932 + minValue,
  933 + maxValue,
  934 + majorTicks,
  935 + minorTicks: settings.minorTicks || 2,
  936 + units: getUnits(this.ctx, settings),
  937 + title: ((settings.showUnitTitle !== false) ?
  938 + (settings.unitTitle && settings.unitTitle.length > 0 ?
  939 + settings.unitTitle : dataKey.label) : ''),
  940 +
  941 + borders: settings.showBorder !== false,
  942 + borderShadowWidth: (settings.showBorder !== false) ? 3 : 0,
  943 +
  944 + // number formats
  945 + valueInt,
  946 + valueDec,
  947 + majorTicksInt: 1,
  948 + majorTicksDec: 0,
  949 +
  950 + valueBox: settings.valueBox !== false,
  951 + valueBoxStroke: 5,
  952 + valueBoxWidth: 0,
  953 + valueText: '',
  954 + valueTextShadow: true,
  955 + valueBoxBorderRadius: 2.5,
  956 +
  957 + // highlights
  958 + highlights: (settings.highlights && settings.highlights.length > 0) ? settings.highlights : highlights,
  959 + highlightsWidth: (isDefined(settings.highlightsWidth) && settings.highlightsWidth !== null) ? settings.highlightsWidth : 15,
  960 +
  961 + // fonts
  962 + fontNumbers: getFontFamily(settings.numbersFont),
  963 + fontTitle: getFontFamily(settings.titleFont),
  964 + fontUnits: getFontFamily(settings.unitsFont),
  965 + fontValue: getFontFamily(settings.valueFont),
  966 +
  967 + fontNumbersSize: settings.numbersFont && settings.numbersFont.size ? settings.numbersFont.size : 18,
  968 + fontTitleSize: settings.titleFont && settings.titleFont.size ? settings.titleFont.size : 24,
  969 + fontUnitsSize: settings.unitsFont && settings.unitsFont.size ? settings.unitsFont.size : 22,
  970 + fontValueSize: settings.valueFont && settings.valueFont.size ? settings.valueFont.size : 40,
  971 +
  972 + fontNumbersStyle: settings.numbersFont && settings.numbersFont.style ? settings.numbersFont.style : 'normal',
  973 + fontTitleStyle: settings.titleFont && settings.titleFont.style ? settings.titleFont.style : 'normal',
  974 + fontUnitsStyle: settings.unitsFont && settings.unitsFont.style ? settings.unitsFont.style : 'normal',
  975 + fontValueStyle: settings.valueFont && settings.valueFont.style ? settings.valueFont.style : 'normal',
  976 +
  977 + fontNumbersWeight: settings.numbersFont && settings.numbersFont.weight ? settings.numbersFont.weight : '500',
  978 + fontTitleWeight: settings.titleFont && settings.titleFont.weight ? settings.titleFont.weight : '500',
  979 + fontUnitsWeight: settings.unitsFont && settings.unitsFont.weight ? settings.unitsFont.weight : '500',
  980 + fontValueWeight: settings.valueFont && settings.valueFont.weight ? settings.valueFont.weight : '500',
  981 +
  982 + colorNumbers: settings.numbersFont && settings.numbersFont.color ? settings.numbersFont.color : colorNumbers,
  983 + colorTitle: settings.titleFont && settings.titleFont.color ? settings.titleFont.color : '#888',
  984 + colorUnits: settings.unitsFont && settings.unitsFont.color ? settings.unitsFont.color : '#888',
  985 + colorValueText: settings.valueFont && settings.valueFont.color ? settings.valueFont.color : '#444',
  986 + colorValueTextShadow: settings.valueFont && settings.valueFont.shadowColor ? settings.valueFont.shadowColor : 'rgba(0,0,0,0.3)',
  987 +
  988 + // colors
  989 + colorPlate: settings.colorPlate || '#fff',
  990 + colorMajorTicks: settings.colorMajorTicks || '#444',
  991 + colorMinorTicks: settings.colorMinorTicks || '#666',
  992 + colorNeedle: settings.colorNeedle || keyColor,
  993 + colorNeedleEnd: settings.colorNeedleEnd || keyColor,
  994 +
  995 + colorValueBoxRect: settings.colorValueBoxRect || '#888',
  996 + colorValueBoxRectEnd: settings.colorValueBoxRectEnd || '#666',
  997 + colorValueBoxBackground: settings.colorValueBoxBackground || '#babab2',
  998 + colorValueBoxShadow: settings.colorValueBoxShadow || 'rgba(0,0,0,1)',
  999 + colorNeedleShadowUp: settings.colorNeedleShadowUp || 'rgba(2,255,255,0.2)',
  1000 + colorNeedleShadowDown: settings.colorNeedleShadowDown || 'rgba(188,143,143,0.45)',
  1001 +
  1002 + // animations
  1003 + animation: settings.animation !== false && !this.ctx.isMobile,
  1004 + animationDuration: (isDefined(settings.animationDuration) && settings.animationDuration !== null) ? settings.animationDuration : 500,
  1005 + animationRule: settings.animationRule || 'cycle'
  1006 + } as O;
  1007 +
  1008 + this.prepareGaugeOptions(settings, gaugeData);
  1009 + return gaugeData;
  1010 + }
  1011 +
  1012 + protected abstract prepareGaugeOptions(settings: S, gaugeData: O);
  1013 +
  1014 +}
  1015 +
  1016 +function getValueDec(ctx: WidgetContext, settings: AnalogueGaugeSettings): number {
  1017 + let dataKey;
  1018 + if (ctx.data && ctx.data[0]) {
  1019 + dataKey = ctx.data[0].dataKey;
  1020 + }
  1021 + if (dataKey && isDefined(dataKey.decimals)) {
  1022 + return dataKey.decimals;
  1023 + } else {
  1024 + return (isDefined(settings.valueDec) && settings.valueDec !== null)
  1025 + ? settings.valueDec : ctx.decimals;
  1026 + }
  1027 +}
  1028 +
  1029 +function getUnits(ctx: WidgetContext, settings: AnalogueGaugeSettings): string {
  1030 + let dataKey;
  1031 + if (ctx.data && ctx.data[0]) {
  1032 + dataKey = ctx.data[0].dataKey;
  1033 + }
  1034 + if (dataKey && dataKey.units && dataKey.units.length) {
  1035 + return dataKey.units;
  1036 + } else {
  1037 + return isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units;
  1038 + }
  1039 +}
... ...
  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 { AnalogueGaugeSettings, analogueGaugeSettingsSchema } from '@home/components/widget/lib/analogue-gauge.models';
  19 +import { deepClone } from '@core/utils';
  20 +
  21 +export interface AnalogueLinearGaugeSettings extends AnalogueGaugeSettings {
  22 + barStrokeWidth: number;
  23 + colorBarStroke: string;
  24 + colorBar: string;
  25 + colorBarEnd: string;
  26 + colorBarProgress: string;
  27 + colorBarProgressEnd: string;
  28 +}
  29 +
  30 +export function getAnalogueLinearGaugeSettingsSchema(): JsonSettingsSchema {
  31 + const analogueLinearGaugeSettingsSchema = deepClone(analogueGaugeSettingsSchema);
  32 + analogueLinearGaugeSettingsSchema.schema.properties =
  33 + {...analogueLinearGaugeSettingsSchema.schema.properties, ...{
  34 + barStrokeWidth: {
  35 + title: 'Bar stroke width',
  36 + type: 'number',
  37 + default: 2.5
  38 + },
  39 + colorBarStroke: {
  40 + title: 'Bar stroke color',
  41 + type: 'string',
  42 + default: null
  43 + },
  44 + colorBar: {
  45 + title: 'Bar background color',
  46 + type: 'string',
  47 + default: '#fff'
  48 + },
  49 + colorBarEnd: {
  50 + title: 'Bar background color - end gradient',
  51 + type: 'string',
  52 + default: '#ddd'
  53 + },
  54 + colorBarProgress: {
  55 + title: 'Progress bar color',
  56 + type: 'string',
  57 + default: null
  58 + },
  59 + colorBarProgressEnd: {
  60 + title: 'Progress bar color - end gradient',
  61 + type: 'string',
  62 + default: null
  63 + }}};
  64 + analogueLinearGaugeSettingsSchema.form.unshift(
  65 + 'barStrokeWidth',
  66 + {
  67 + key: 'colorBarStroke',
  68 + type: 'color'
  69 + },
  70 + {
  71 + key: 'colorBar',
  72 + type: 'color'
  73 + },
  74 + {
  75 + key: 'colorBarEnd',
  76 + type: 'color'
  77 + },
  78 + {
  79 + key: 'colorBarProgress',
  80 + type: 'color'
  81 + },
  82 + {
  83 + key: 'colorBarProgressEnd',
  84 + type: 'color'
  85 + }
  86 + );
  87 + return analogueLinearGaugeSettingsSchema;
  88 +}
... ...
  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 { JsonSettingsSchema } from '@shared/models/widget.models';
  19 +import { WidgetContext } from '@home/models/widget-component.models';
  20 +import { TbAnalogueGauge } from '@home/components/widget/lib/analogue-gauge.models';
  21 +import {
  22 + AnalogueLinearGaugeSettings,
  23 + getAnalogueLinearGaugeSettingsSchema
  24 +} from '@home/components/widget/lib/analogue-linear-gauge.models';
  25 +import { isDefined } from '@core/utils';
  26 +import * as tinycolor_ from 'tinycolor2';
  27 +import LinearGaugeOptions = CanvasGauges.LinearGaugeOptions;
  28 +import LinearGauge = CanvasGauges.LinearGauge;
  29 +import BaseGauge = CanvasGauges.BaseGauge;
  30 +
  31 +const tinycolor = tinycolor_;
  32 +
  33 +const analogueLinearGaugeSettingsSchemaValue = getAnalogueLinearGaugeSettingsSchema();
  34 +
  35 +export class TbAnalogueLinearGauge extends TbAnalogueGauge<AnalogueLinearGaugeSettings,LinearGaugeOptions>{
  36 +
  37 + static get settingsSchema(): JsonSettingsSchema {
  38 + return analogueLinearGaugeSettingsSchemaValue;
  39 + }
  40 +
  41 + constructor(ctx: WidgetContext, canvasId: string) {
  42 + super(ctx, canvasId);
  43 + }
  44 +
  45 + protected prepareGaugeOptions(settings: AnalogueLinearGaugeSettings, gaugeData: LinearGaugeOptions) {
  46 + const dataKey = this.ctx.data[0].dataKey;
  47 + const keyColor = settings.defaultColor || dataKey.color;
  48 +
  49 + const barStrokeColor = tinycolor(keyColor).darken().setAlpha(0.6).toRgbString();
  50 + const progressColorStart = tinycolor(keyColor).setAlpha(0.05).toRgbString();
  51 + const progressColorEnd = tinycolor(keyColor).darken().toRgbString();
  52 +
  53 + gaugeData.barStrokeWidth = (isDefined(settings.barStrokeWidth) && settings.barStrokeWidth !== null) ? settings.barStrokeWidth : 2.5;
  54 + gaugeData.colorBarStroke = settings.colorBarStroke || barStrokeColor;
  55 + gaugeData.colorBar = settings.colorBar || '#fff';
  56 + gaugeData.colorBarEnd = settings.colorBarEnd || '#ddd';
  57 + gaugeData.colorBarProgress = settings.colorBarProgress || progressColorStart;
  58 + gaugeData.colorBarProgressEnd = settings.colorBarProgressEnd || progressColorEnd;
  59 + }
  60 +
  61 + protected createGauge(gaugeData: LinearGaugeOptions): BaseGauge {
  62 + return new LinearGauge(gaugeData);
  63 + }
  64 +
  65 +}
... ...
  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 { AnalogueGaugeSettings, analogueGaugeSettingsSchema } from '@home/components/widget/lib/analogue-gauge.models';
  19 +import { deepClone } from '@core/utils';
  20 +
  21 +export interface AnalogueRadialGaugeSettings extends AnalogueGaugeSettings {
  22 + startAngle: number;
  23 + ticksAngle: number;
  24 + needleCircleSize: number;
  25 +}
  26 +
  27 +export function getAnalogueRadialGaugeSettingsSchema(): JsonSettingsSchema {
  28 + const analogueRadialGaugeSettingsSchema = deepClone(analogueGaugeSettingsSchema);
  29 + analogueRadialGaugeSettingsSchema.schema.properties =
  30 + {...analogueRadialGaugeSettingsSchema.schema.properties, ...{
  31 + startAngle: {
  32 + title: 'Start ticks angle',
  33 + type: 'number',
  34 + default: 45
  35 + },
  36 + ticksAngle: {
  37 + title: 'Ticks angle',
  38 + type: 'number',
  39 + default: 270
  40 + },
  41 + needleCircleSize: {
  42 + title: 'Needle circle size',
  43 + type: 'number',
  44 + default: 10
  45 + }}};
  46 + analogueRadialGaugeSettingsSchema.form.unshift(
  47 + 'startAngle',
  48 + 'ticksAngle',
  49 + 'needleCircleSize'
  50 + );
  51 + return analogueRadialGaugeSettingsSchema;
  52 +}
... ...
  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 {
  19 + AnalogueRadialGaugeSettings, getAnalogueRadialGaugeSettingsSchema
  20 +} from '@home/components/widget/lib/analogue-radial-gauge.models';
  21 +import { JsonSettingsSchema } from '@shared/models/widget.models';
  22 +import { WidgetContext } from '@home/models/widget-component.models';
  23 +import { TbAnalogueGauge } from '@home/components/widget/lib/analogue-gauge.models';
  24 +import RadialGauge = CanvasGauges.RadialGauge;
  25 +import RadialGaugeOptions = CanvasGauges.RadialGaugeOptions;
  26 +import BaseGauge = CanvasGauges.BaseGauge;
  27 +
  28 +const analogueRadialGaugeSettingsSchemaValue = getAnalogueRadialGaugeSettingsSchema();
  29 +
  30 +export class TbAnalogueRadialGauge extends TbAnalogueGauge<AnalogueRadialGaugeSettings,RadialGaugeOptions>{
  31 +
  32 + static get settingsSchema(): JsonSettingsSchema {
  33 + return analogueRadialGaugeSettingsSchemaValue;
  34 + }
  35 +
  36 + constructor(ctx: WidgetContext, canvasId: string) {
  37 + super(ctx, canvasId);
  38 + }
  39 +
  40 + protected prepareGaugeOptions(settings: AnalogueRadialGaugeSettings, gaugeData: RadialGaugeOptions) {
  41 + gaugeData.ticksAngle = settings.ticksAngle || 270;
  42 + gaugeData.startAngle = settings.startAngle || 45;
  43 +
  44 + // colors
  45 +
  46 + gaugeData.colorNeedleCircleOuter = '#f0f0f0';
  47 + gaugeData.colorNeedleCircleOuterEnd = '#ccc';
  48 + gaugeData.colorNeedleCircleInner = '#e8e8e8'; // tinycolor(keyColor).lighten(30).toRgbString(),//'#e8e8e8',
  49 + gaugeData.colorNeedleCircleInnerEnd = '#f5f5f5';
  50 +
  51 + // needle
  52 + gaugeData.needleCircleSize = settings.needleCircleSize || 10;
  53 + gaugeData.needleCircleInner = true;
  54 + gaugeData.needleCircleOuter = true;
  55 +
  56 + // custom animations
  57 + gaugeData.animationTarget = 'needle'; // 'needle' or 'plate'
  58 + }
  59 +
  60 + protected createGauge(gaugeData: RadialGaugeOptions): BaseGauge {
  61 + return new RadialGauge(gaugeData);
  62 + }
  63 +
  64 +}
... ...
  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 +export type FontStyle = 'normal' | 'italic' | 'oblique';
  19 +export type FontWeight = 'normal' | 'bold' | 'bolder' | 'lighter'
  20 + | '100' | '200' | '300' | '400' | '500'
  21 + | '600' | '700' | '800' | '900';
  22 +
  23 +export interface FontSettings {
  24 + family: string;
  25 + size: number;
  26 + style: FontStyle;
  27 + weight: FontWeight;
  28 + color: string;
  29 + shadowColor?: string;
  30 +}
  31 +
  32 +export function getFontFamily(fontSettings: FontSettings): string {
  33 + let family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto';
  34 + if (family === 'RobotoDraft') {
  35 + family = 'Roboto';
  36 + }
  37 + return family;
  38 +}
... ...
... ... @@ -37,17 +37,11 @@ import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.c
37 37 import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
38 38 import { WINDOW } from '@core/services/window.service';
39 39
40   -import * as tinycolor_ from 'tinycolor2';
41   -import { TbFlot } from './lib/flot-widget';
42 40 import { NULL_UUID } from '@shared/models/id/has-uuid';
43 41 import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
44 42 import { TenantId } from '@app/shared/models/id/tenant-id';
45 43 import { SharedModule } from '@shared/shared.module';
46 44
47   -const tinycolor = tinycolor_;
48   -
49   -// declare var jQuery: any;
50   -
51 45 // @dynamic
52 46 @Injectable()
53 47 export class WidgetComponentService {
... ... @@ -70,12 +64,6 @@ export class WidgetComponentService {
70 64 private utils: UtilsService,
71 65 private resources: ResourcesService,
72 66 private translate: TranslateService) {
73   - // @ts-ignore
74   - this.window.tinycolor = tinycolor;
75   - // @ts-ignore
76   - this.window.cssjs = cssjs;
77   - // @ts-ignore
78   - this.window.TbFlot = TbFlot;
79 67
80 68 this.cssParser.testMode = false;
81 69
... ...
... ... @@ -168,11 +168,11 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
168 168 }
169 169 }
170 170
171   - private onModelChange(key: (string | number)[], val: any) {
  171 + private onModelChange(key: (string | number)[], val: any, forceUpdate = false) {
172 172 if (isString(val) && val === '') {
173 173 val = undefined;
174 174 }
175   - if (JsonFormUtils.updateValue(key, this.model, val)) {
  175 + if (JsonFormUtils.updateValue(key, this.model, val) || forceUpdate) {
176 176 this.formProps.model = this.model;
177 177 this.isModelValid = this.validateModel();
178 178 this.updateView();
... ...
... ... @@ -96,7 +96,7 @@ class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardAr
96 96 keys: newKeys
97 97 }
98 98 );
99   - this.props.onChangeValidate(this.state.model);
  99 + this.props.onChangeValidate(this.state.model, true);
100 100 }
101 101
102 102 onDelete(index: number) {
... ... @@ -110,7 +110,7 @@ class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardAr
110 110 keys: newKeys
111 111 }
112 112 );
113   - this.props.onChangeValidate(this.state.model);
  113 + this.props.onChangeValidate(this.state.model, true);
114 114 }
115 115
116 116 setIndex(index: number) {
... ...
... ... @@ -38,7 +38,7 @@ export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps>
38 38 }*/
39 39 }
40 40
41   - onChangeValidate(e) {
  41 + onChangeValidate(e, forceUpdate?: boolean) {
42 42 let value = null;
43 43 if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') {
44 44 if (e.target.value === null || e.target.value === '') {
... ... @@ -61,7 +61,7 @@ export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps>
61 61 valid: validationResult.valid,
62 62 error: validationResult.valid ? null : validationResult.error.message
63 63 });
64   - this.props.onChange(this.props.form.key, value);
  64 + this.props.onChange(this.props.form.key, value, forceUpdate);
65 65 }
66 66
67 67 defaultValue() {
... ...
... ... @@ -6,7 +6,6 @@ import FormControl from '@material-ui/core/FormControl';
6 6 import ThingsboardBaseComponent from '@shared/components/json-form/react/json-form-base-component';
7 7
8 8 class ThingsboardRadios extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
9   -
10 9 render() {
11 10 const items = this.props.form.titleMap.map((item, index) => {
12 11 return (
... ... @@ -19,7 +18,9 @@ class ThingsboardRadios extends React.Component<JsonFormFieldProps, JsonFormFiel
19 18 className={this.props.form.htmlClass}
20 19 disabled={this.props.form.readonly}>
21 20 <FormLabel component='legend'>{this.props.form.title}</FormLabel>
22   - <RadioGroup name={this.props.form.title} value={this.props.value} onChange={this.props.onChangeValidate}>
  21 + <RadioGroup name={this.props.form.title} value={this.props.value} onChange={(e) => {
  22 + this.props.onChangeValidate(e);
  23 + }}>
23 24 {items}
24 25 </RadioGroup>
25 26 </FormControl>
... ...
... ... @@ -73,8 +73,8 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
73 73 this.hasConditions = false;
74 74 }
75 75
76   - onChange(key: (string | number)[], val: any) {
77   - this.props.onModelChange(key, val);
  76 + onChange(key: (string | number)[], val: any, forceUpdate?: boolean) {
  77 + this.props.onModelChange(key, val, forceUpdate);
78 78 if (this.hasConditions) {
79 79 this.forceUpdate();
80 80 }
... ...
... ... @@ -68,7 +68,9 @@ class ThingsboardText extends React.Component<JsonFormFieldProps, ThingsboardTex
68 68 multiline={multiline}
69 69 error={!this.props.valid}
70 70 helperText={this.props.valid ? this.props.form.placeholder : this.props.error}
71   - onChange={this.props.onChangeValidate}
  71 + onChange={(e) => {
  72 + this.props.onChangeValidate(e);
  73 + }}
72 74 defaultValue={this.props.value}
73 75 disabled={this.props.form.readonly}
74 76 rows={rows}
... ...
... ... @@ -50,7 +50,7 @@ export interface GroupInfo {
50 50 GroupTitle: string;
51 51 }
52 52
53   -export type onChangeFn = (key: (string | number)[], val: any) => void;
  53 +export type onChangeFn = (key: (string | number)[], val: any, forceUpdate?: boolean) => void;
54 54 export type OnColorClickFn = (key: (string | number)[], val: tinycolor.ColorFormats.RGBA,
55 55 colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) => void;
56 56 export type onToggleFullscreenFn = (element: HTMLElement, fullscreenFinishFn?: () => void) => void;
... ... @@ -122,7 +122,7 @@ export interface JsonFormFieldProps {
122 122 mapper?: {[type: string]: any};
123 123 onChange?: onChangeFn;
124 124 onColorClick?: OnColorClickFn;
125   - onChangeValidate?: (e: any) => void;
  125 + onChangeValidate?: (e: any, forceUpdate?: boolean) => void;
126 126 onToggleFullscreen?: onToggleFullscreenFn;
127 127 valid?: boolean;
128 128 error?: string;
... ...
... ... @@ -81,3 +81,24 @@ import 'core-js/es/array';
81 81 */
82 82
83 83 (window as any).global = window;
  84 +
  85 +/***************************************************************************************************
  86 + * WIDGETS IMPORTS
  87 + */
  88 +
  89 +import cssjs from '@core/css/css';
  90 +import { TbFlot } from '@home/components/widget/lib/flot-widget';
  91 +import { TbAnalogueCompass } from '@home/components/widget/lib/analogue-compass';
  92 +import { TbAnalogueRadialGauge } from '@home/components/widget/lib/analogue-radial-gauge';
  93 +import { TbAnalogueLinearGauge } from '@home/components/widget/lib/analogue-linear-gauge';
  94 +
  95 +import * as tinycolor_ from 'tinycolor2';
  96 +
  97 +const tinycolor = tinycolor_;
  98 +
  99 +(window as any).tinycolor = tinycolor;
  100 +(window as any).cssjs = cssjs;
  101 +(window as any).TbFlot = TbFlot;
  102 +(window as any).TbAnalogueCompass = TbAnalogueCompass;
  103 +(window as any).TbAnalogueRadialGauge = TbAnalogueRadialGauge;
  104 +(window as any).TbAnalogueLinearGauge = TbAnalogueLinearGauge;
... ...
  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 +@font-face {
  17 + font-family: "Segment7Standard";
  18 + font-style: italic;
  19 + font-weight: 400;
  20 + src: url("data:font/opentype;charset=utf-8;base64,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AAEAAAAIAAAAAAAEAAAAAAAAAAEAAAAAzD2izwAAAADPr89TAAAAAM+4fiECSQAAAkkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpwBCAC8ALwAvAFwBpwAvAC8ALwBcAC8AXAIBAC8ALwGWAC8ALwBCAC4ALgBYAC8ALwBcAacALwAvAC8ALwAvAC8ALwAvAC8ALwAvAC8ALwGWAC8ALwAvAC8ALwAvAC8AQgAvAC4ALwAvAC8ALwAvAC8ALwAvAEIALwBCAFwBpwAvAC8ALwAvAC8ALwAvAC8BlgAvAC8ALwAvAC8ALwAvAEIALwAuAC8ALwAvAC8ALwAvAC8AAAGWAAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8ALwAAAAAAAAAAAAAAAAAAAC8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") format("opentype");
  21 +}
... ...
... ... @@ -22,6 +22,7 @@
22 22 @import './scss/constants';
23 23 @import './scss/animations';
24 24 @import './scss/mixins';
  25 +@import './scss/fonts';
25 26
26 27 body, html {
27 28 height: 100%;
... ...
... ... @@ -2,7 +2,7 @@
2 2 "extends": "../tsconfig.json",
3 3 "compilerOptions": {
4 4 "outDir": "../out-tsc/app",
5   - "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom", "jstree", "raphael"]
  5 + "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom", "jstree", "raphael", "canvas-gauges"]
6 6 },
7 7 "angularCompilerOptions": {
8 8 "fullTemplateTypeCheck": true
... ...