Commit c4e4f8953a661f204a38ed57e882154e670b7ed8

Authored by Igor Kulikov
1 parent 496286cb

TB-70: Add knob control widget.

@@ -36,6 +36,22 @@ @@ -36,6 +36,22 @@
36 "dataKeySettingsSchema": "{}\n", 36 "dataKeySettingsSchema": "{}\n",
37 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC remote shell\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 37 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC remote shell\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
38 } 38 }
  39 + },
  40 + {
  41 + "alias": "shiny_knob_control",
  42 + "name": "Shiny Knob Control",
  43 + "descriptor": {
  44 + "type": "rpc",
  45 + "sizeX": 2.5,
  46 + "sizeY": 3,
  47 + "resources": [],
  48 + "templateHtml": "<tb-shiny-knob ctx='ctx'></tb-shiny-knob>",
  49 + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel md-switch {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n padding-left: 8px;\n padding-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n padding-left: 4px;\n padding-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",
  50 + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n",
  51 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"theme\": {\n \"title\": \"Knob theme\",\n \"type\": \"string\",\n \"default\": \"light\"\n }, \n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n {\n \"key\": \"theme\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"light\",\n \"label\": \"Light\"\n },\n {\n \"value\": \"dark\",\n \"label\": \"Dark\"\n }\n ]\n },\n \"requestTimeout\"\n ]\n}",
  52 + "dataKeySettingsSchema": "{}\n",
  53 + "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"theme\":\"light\",\"initialValue\":50},\"title\":\"Shiny Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
  54 + }
39 } 55 }
40 ] 56 ]
41 } 57 }
@@ -22,6 +22,8 @@ import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-wid @@ -22,6 +22,8 @@ import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-wid
22 import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; 22 import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget';
23 import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget'; 23 import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget';
24 24
  25 +import thingsboardRpcWidgets from '../widget/lib/rpc';
  26 +
25 import TbFlot from '../widget/lib/flot-widget'; 27 import TbFlot from '../widget/lib/flot-widget';
26 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; 28 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
27 import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge'; 29 import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge';
@@ -39,7 +41,7 @@ import thingsboardTypes from '../common/types.constant'; @@ -39,7 +41,7 @@ import thingsboardTypes from '../common/types.constant';
39 import thingsboardUtils from '../common/utils.service'; 41 import thingsboardUtils from '../common/utils.service';
40 42
41 export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, 43 export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
42 - thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardTypes, thingsboardUtils]) 44 + thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils])
43 .factory('widgetService', WidgetService) 45 .factory('widgetService', WidgetService)
44 .name; 46 .name;
45 47
@@ -95,6 +95,12 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { @@ -95,6 +95,12 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
95 options.value = options.minValue; 95 options.value = options.minValue;
96 } 96 }
97 97
  98 + if (options.gaugeType === 'donut') {
  99 + if (!options.donutStartAngle) {
  100 + options.donutStartAngle = 1.5 * Math.PI;
  101 + }
  102 + }
  103 +
98 var colorsCount = options.levelColors.length; 104 var colorsCount = options.levelColors.length;
99 var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; 105 var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
100 options.colorsRange = []; 106 options.colorsRange = [];
@@ -473,7 +479,7 @@ function drawBackground(context, options) { @@ -473,7 +479,7 @@ function drawBackground(context, options) {
473 context.lineCap = 'round'; 479 context.lineCap = 'round';
474 } 480 }
475 if (options.gaugeType === 'donut') { 481 if (options.gaugeType === 'donut') {
476 - context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, 1.5 * Math.PI, 3.5 * Math.PI); 482 + context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, options.donutStartAngle, options.donutStartAngle + 2 * Math.PI);
477 context.stroke(); 483 context.stroke();
478 } else if (options.gaugeType === 'arc') { 484 } else if (options.gaugeType === 'arc') {
479 context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, Math.PI, 2*Math.PI); 485 context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, Math.PI, 2*Math.PI);
@@ -605,7 +611,7 @@ function getProgressColor(progress, colorsRange) { @@ -605,7 +611,7 @@ function getProgressColor(progress, colorsRange) {
605 } 611 }
606 } 612 }
607 613
608 -function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut) { 614 +function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut, donutStartAngle) {
609 context.setLineDash([]); 615 context.setLineDash([]);
610 var strokeWidth = Ro - Ri; 616 var strokeWidth = Ro - Ri;
611 var blur = 0.55; 617 var blur = 0.55;
@@ -623,7 +629,7 @@ function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut) { @@ -623,7 +629,7 @@ function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut) {
623 context.beginPath(); 629 context.beginPath();
624 var e = 0.01 * Math.PI; 630 var e = 0.01 * Math.PI;
625 if (isDonut) { 631 if (isDonut) {
626 - context.arc(Cx, Cy, Rm, 1.5 * Math.PI - e, 1.5 * Math.PI + 2 * Math.PI * progress + e); 632 + context.arc(Cx, Cy, Rm, donutStartAngle - e, donutStartAngle + 2 * Math.PI * progress + e);
627 } else { 633 } else {
628 context.arc(Cx, Cy, Rm, Math.PI - e, Math.PI + Math.PI * progress + e); 634 context.arc(Cx, Cy, Rm, Math.PI - e, Math.PI + Math.PI * progress + e);
629 } 635 }
@@ -682,10 +688,10 @@ function drawProgress(context, options, progress) { @@ -682,10 +688,10 @@ function drawProgress(context, options, progress) {
682 context.strokeStyle = neonColor; 688 context.strokeStyle = neonColor;
683 } 689 }
684 context.beginPath(); 690 context.beginPath();
685 - context.arc(Cx, Cy, Rm, 1.5 * Math.PI, 1.5 * Math.PI + 2 * Math.PI * progress); 691 + context.arc(Cx, Cy, Rm, options.donutStartAngle, options.donutStartAngle + 2 * Math.PI * progress);
686 context.stroke(); 692 context.stroke();
687 if (options.neonGlowBrightness && !options.isMobile) { 693 if (options.neonGlowBrightness && !options.isMobile) {
688 - drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true); 694 + drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, options.donutStartAngle);
689 } 695 }
690 } else if (options.gaugeType === 'arc') { 696 } else if (options.gaugeType === 'arc') {
691 if (options.neonGlowBrightness) { 697 if (options.neonGlowBrightness) {
  1 +/*
  2 + * Copyright © 2016-2017 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 tbShinyKnob from './shiny-knob.directive';
  18 +
  19 +export default angular.module('thingsboard.widgets.rpc', [
  20 + tbShinyKnob
  21 +]).name;
  1 +/*
  2 + * Copyright © 2016-2017 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 './shiny-knob.scss';
  18 +
  19 +import CanvasDigitalGauge from './../CanvasDigitalGauge';
  20 +//import tinycolor from 'tinycolor2';
  21 +
  22 +/* eslint-disable import/no-unresolved, import/default */
  23 +
  24 +import shinyKnobTemplate from './shiny-knob.tpl.html';
  25 +
  26 +/* eslint-enable import/no-unresolved, import/default */
  27 +
  28 +export default angular.module('thingsboard.widgets.rpc.shinyKnob', [])
  29 + .directive('tbShinyKnob', ShinyKnob)
  30 + .name;
  31 +
  32 +/*@ngInject*/
  33 +function ShinyKnob() {
  34 + return {
  35 + restrict: "E",
  36 + scope: true,
  37 + bindToController: {
  38 + ctx: '='
  39 + },
  40 + controller: ShinyKnobController,
  41 + controllerAs: 'vm',
  42 + templateUrl: shinyKnobTemplate
  43 + };
  44 +}
  45 +
  46 +/*@ngInject*/
  47 +function ShinyKnobController($element, $scope, $document) {
  48 + let vm = this;
  49 +
  50 + vm.value = 0;
  51 +
  52 + var snap = 0;
  53 +
  54 + var knob = angular.element('.knob', $element),
  55 + knobContainer = angular.element('#knob-container', $element),
  56 + knobTop = knob.find('.top'),
  57 + startDeg = -1,
  58 + currentDeg = 0,
  59 + rotation = 0,
  60 + lastDeg = 0;
  61 +
  62 + var canvasBarElement = angular.element('#canvasBar', $element);
  63 +
  64 + var levelColors = ['rgb(0, 128, 0)', 'rgb(251, 192, 45)', 'rgb(244, 67, 54)'];
  65 + var canvasBar;
  66 +
  67 + $scope.$watch('vm.ctx', () => {
  68 + if (vm.ctx) {
  69 + init();
  70 + }
  71 + });
  72 +
  73 + function init() {
  74 +
  75 + vm.minValue = angular.isDefined(vm.ctx.settings.minValue) ? vm.ctx.settings.minValue : 0;
  76 + vm.maxValue = angular.isDefined(vm.ctx.settings.maxValue) ? vm.ctx.settings.maxValue : 100;
  77 +
  78 + vm.darkTheme = vm.ctx.settings.theme == 'dark';
  79 +
  80 + var canvasBarData = {
  81 + renderTo: canvasBarElement[0],
  82 + hideValue: true,
  83 + neonGlowBrightness: vm.darkTheme ? 40 : 0,
  84 + gaugeWidthScale: 0.5,
  85 + gaugeColor: vm.darkTheme ? 'rgb(23, 26, 28)' : 'rgba(0,0,0,0)',
  86 + levelColors: levelColors,
  87 + minValue: vm.minValue,
  88 + maxValue: vm.maxValue,
  89 + gaugeType: 'donut',
  90 + dashThickness: 1.5,
  91 + donutStartAngle: Math.PI,
  92 + animation: false,
  93 + animationDuration: 250,
  94 + animationRule: 'linear'
  95 + };
  96 +
  97 + canvasBar = new CanvasDigitalGauge(canvasBarData).draw();
  98 +
  99 + knob.on('mousedown touchstart', (e) => {
  100 + e.preventDefault();
  101 + var offset = knob.offset();
  102 + var center = {
  103 + y : offset.top + knob.height()/2,
  104 + x: offset.left + knob.width()/2
  105 + };
  106 +
  107 + var a, b, deg, tmp,
  108 + rad2deg = 180/Math.PI;
  109 +
  110 + knob.on('mousemove.rem touchmove.rem', (e) => {
  111 +
  112 + e = (e.originalEvent.touches) ? e.originalEvent.touches[0] : e;
  113 +
  114 + a = center.y - e.pageY;
  115 + b = center.x - e.pageX;
  116 + deg = Math.atan2(a,b)*rad2deg;
  117 +
  118 + if(deg<0){
  119 + deg = 360 + deg;
  120 + }
  121 +
  122 + if(startDeg == -1){
  123 + startDeg = deg;
  124 + }
  125 +
  126 + tmp = Math.floor((deg-startDeg) + rotation);
  127 +
  128 + if(tmp < 0){
  129 + tmp = 360 + tmp;
  130 + }
  131 + else if(tmp > 359){
  132 + tmp = tmp % 360;
  133 + }
  134 +
  135 + if(snap && tmp < snap){
  136 + tmp = 0;
  137 + }
  138 + if(Math.abs(tmp - lastDeg) > 180){
  139 + return false;
  140 + }
  141 + currentDeg = tmp;
  142 + lastDeg = tmp;
  143 +
  144 + knobTop.css('transform','rotate('+(currentDeg)+'deg)');
  145 + turn(currentDeg/359);
  146 + });
  147 +
  148 + $document.on('mouseup.rem touchend.rem',() => {
  149 + knob.off('.rem');
  150 + $document.off('.rem');
  151 + rotation = currentDeg;
  152 + startDeg = -1;
  153 + });
  154 +
  155 + });
  156 +
  157 + vm.ctx.resize = resize;
  158 + resize();
  159 +
  160 + var initialValue = angular.isDefined(vm.ctx.settings.initialValue) ? vm.ctx.settings.initialValue : vm.minValue;
  161 +
  162 + setValue(initialValue);
  163 + }
  164 +
  165 + function resize() {
  166 + var width = knobContainer.width();
  167 + var height = knobContainer.height();
  168 + var size = Math.min(width, height);
  169 + knob.css({width: size, height: size});
  170 + canvasBar.update({width: size, height: size});
  171 + }
  172 +
  173 + function turn(ratio) {
  174 + var value = (vm.minValue + (vm.maxValue - vm.minValue)*ratio).toFixed(2);
  175 + if (canvasBar.value != value) {
  176 + canvasBar.value = value;
  177 + }
  178 + onValue(value);
  179 + }
  180 +
  181 + function setValue(value) {
  182 + var ratio = (value-vm.minValue) / (vm.maxValue - vm.minValue);
  183 + rotation = lastDeg = currentDeg = ratio*360;
  184 + knobTop.css('transform','rotate('+(currentDeg)+'deg)');
  185 + if (canvasBar.value != value) {
  186 + canvasBar.value = value;
  187 + }
  188 + vm.value = value;
  189 + }
  190 +
  191 + function onValue(value) {
  192 + console.log(`onValue ${value}`); //eslint-disable-line
  193 + $scope.$applyAsync(() => {
  194 + vm.value = value;
  195 + });
  196 + }
  197 +
  198 +}
  1 +/**
  2 + * Copyright © 2016-2017 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 +$knob-img: url('./knob.png');
  18 +
  19 +$bars-margin-pct: percentage(0.2);
  20 +$shadow-size: 5;
  21 +$shadow-size-px: $shadow-size+px;
  22 +$shadow-offset-px: $shadow-size/2+px;
  23 +
  24 +.tb-shiny-knob {
  25 + width:100%;
  26 + height:100%;
  27 + &.dark {
  28 + background: #000;//$dark-bg-img #1f2129;
  29 + }
  30 +
  31 + .knob {
  32 + position: relative;
  33 + &[draggable] {
  34 + -moz-user-select: none;
  35 + -webkit-user-select: none;
  36 + user-select: none;
  37 + }
  38 + #canvasBar {
  39 + position:absolute;
  40 + top:0;
  41 + left:0;
  42 + bottom: 0;
  43 + right: 0;
  44 + }
  45 + .top{
  46 + position:absolute;
  47 + top: calc(#{$bars-margin-pct} - #{$shadow-offset-px});
  48 + left: $bars-margin-pct;
  49 + bottom: calc(#{$bars-margin-pct} + #{$shadow-offset-px});
  50 + right: $bars-margin-pct;
  51 + background:$knob-img no-repeat;
  52 + background-size: contain;
  53 + z-index:10;
  54 + cursor:default !important;
  55 + &:after {
  56 + content:'';
  57 + width:10px;
  58 + height:10px;
  59 + background-color:#666;
  60 + position:absolute;
  61 + top:50%;
  62 + left:10px;
  63 + margin-top:-5px;
  64 + border-radius: 50%;
  65 + cursor:default !important;
  66 + box-shadow: 0 0 1px #5a5a5a inset;
  67 + }
  68 + }
  69 + .base{
  70 + top: calc(#{$bars-margin-pct} - #{$shadow-offset-px});
  71 + left: $bars-margin-pct;
  72 + bottom: calc(#{$bars-margin-pct} + #{$shadow-offset-px});
  73 + right: $bars-margin-pct;
  74 + border-radius:50%;
  75 + box-shadow:0 $shadow-size-px 0 #4a5056,$shadow-size-px $shadow-size-px $shadow-size-px #000;
  76 + position:absolute;
  77 + z-index:1;
  78 + }
  79 + }
  80 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  19 +<div class="tb-shiny-knob" layout="column" ng-class="{'dark': vm.darkTheme}">
  20 + <div layout="row" layout-align="center start" class="md-padding">
  21 + <span>{{ vm.value }}</span>
  22 + </div>
  23 + <div id="knob-container" flex layout="column" layout-align="center center">
  24 + <div class="knob">
  25 + <canvas id="canvasBar"></canvas>
  26 + <div class="top"></div>
  27 + <div class="base"></div>
  28 + </div>
  29 + </div>
  30 +</div>