Browse Source

Merge branch 'AD/imp-widget/gauge/simple' of github.com:ArtemDzhereleiko/thingsboard into AD/imp-widget/gauge/simple]

pull/10557/head
Vladyslav_Prykhodko 2 years ago
parent
commit
2bb170cdef
  1. 4
      application/src/main/data/json/system/widget_types/digital_horizontal_bar.json
  2. 4
      application/src/main/data/json/system/widget_types/digital_speedometer.json
  3. 4
      application/src/main/data/json/system/widget_types/digital_thermometer.json
  4. 4
      application/src/main/data/json/system/widget_types/digital_vertical_bar.json
  5. 4
      application/src/main/data/json/system/widget_types/gauge.json
  6. 4
      application/src/main/data/json/system/widget_types/horizontal_bar.json
  7. 4
      application/src/main/data/json/system/widget_types/lcd_bar_gauge.json
  8. 4
      application/src/main/data/json/system/widget_types/lcd_gauge.json
  9. 4
      application/src/main/data/json/system/widget_types/mini_gauge.json
  10. 4
      application/src/main/data/json/system/widget_types/neon_gauge.json
  11. 4
      application/src/main/data/json/system/widget_types/simple_gauge.json
  12. 4
      application/src/main/data/json/system/widget_types/simple_neon_gauge.json
  13. 4
      application/src/main/data/json/system/widget_types/vertical_bar.json
  14. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  15. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-value-card-basic-config.component.ts
  16. 175
      ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html
  17. 276
      ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.ts
  18. 28
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html
  19. 10
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.ts
  20. 10
      ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts
  21. 88
      ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts
  22. 69
      ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts
  23. 21
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts
  24. 24
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts
  25. 14
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts
  26. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html
  27. 48
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html
  28. 78
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss
  29. 28
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts
  30. 56
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html
  31. 42
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.scss
  32. 109
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts
  33. 123
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html
  34. 36
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss
  35. 123
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts
  36. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html
  37. 21
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html
  38. 21
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss
  39. 38
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts
  40. 77
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts
  41. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss
  42. 208
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html
  43. 145
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss
  44. 266
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts
  45. 77
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.html
  46. 38
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.scss
  47. 191
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts
  48. 114
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html
  49. 11
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts
  50. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  51. 596
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html
  52. 284
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts
  53. 60
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.html
  54. 147
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts
  55. 44
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html
  56. 32
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts
  57. 28
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html
  58. 11
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts
  59. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  60. 1
      ui-ngx/src/app/shared/components/color-input.component.html
  61. 4
      ui-ngx/src/app/shared/components/color-input.component.scss
  62. 4
      ui-ngx/src/app/shared/components/color-input.component.ts
  63. 449
      ui-ngx/src/app/shared/models/widget-settings.models.ts
  64. 53
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  65. 31
      ui-ngx/src/assets/widget/simple-gauge/arc-layout.svg
  66. 29
      ui-ngx/src/assets/widget/simple-gauge/donut-layout.svg
  67. 31
      ui-ngx/src/assets/widget/simple-gauge/horizontal-bar-layout.svg
  68. 31
      ui-ngx/src/assets/widget/simple-gauge/vertical-bar-layout.svg

4
application/src/main/data/json/system/widget_types/digital_horizontal_bar.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [

4
application/src/main/data/json/system/widget_types/digital_speedometer.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [

4
application/src/main/data/json/system/widget_types/digital_thermometer.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"minValue\":-60,\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"minValue\":-60,\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [

4
application/src/main/data/json/system/widget_types/digital_vertical_bar.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [

4
application/src/main/data/json/system/widget_types/gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [

4
application/src/main/data/json/system/widget_types/horizontal_bar.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}

4
application/src/main/data/json/system/widget_types/lcd_bar_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}

4
application/src/main/data/json/system/widget_types/lcd_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}

4
application/src/main/data/json/system/widget_types/mini_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}

4
application/src/main/data/json/system/widget_types/neon_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null
}

4
application/src/main/data/json/system/widget_types/simple_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}

4
application/src/main/data/json/system/widget_types/simple_neon_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null
}

4
application/src/main/data/json/system/widget_types/vertical_bar.json

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
"hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}

12
ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts

@ -129,6 +129,9 @@ import {
import {
RadarChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/radar-chart-basic-config.component';
import {
DigitalSimpleGaugeBasicConfigComponent
} from '@home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component';
@NgModule({
declarations: [
@ -171,7 +174,8 @@ import {
PieChartBasicConfigComponent,
BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent,
RadarChartBasicConfigComponent
RadarChartBasicConfigComponent,
DigitalSimpleGaugeBasicConfigComponent
],
imports: [
CommonModule,
@ -216,7 +220,8 @@ import {
PieChartBasicConfigComponent,
BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent,
RadarChartBasicConfigComponent
RadarChartBasicConfigComponent,
DigitalSimpleGaugeBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -255,5 +260,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-pie-chart-basic-config': PieChartBasicConfigComponent,
'tb-bar-chart-basic-config': BarChartBasicConfigComponent,
'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent,
'tb-radar-chart-basic-config': RadarChartBasicConfigComponent
'tb-radar-chart-basic-config': RadarChartBasicConfigComponent,
'tb-digital-simple-gauge-basic-config': DigitalSimpleGaugeBasicConfigComponent
};

12
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-value-card-basic-config.component.ts

@ -148,11 +148,13 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
color: {
color: 'rgba(0, 0, 0, 0.87)',
type: ColorType.range,
rangeList: [
{to: 0, color: '#198038'},
{from: 0, to: 0, color: 'rgba(0, 0, 0, 0.87)'},
{from: 0, color: '#D12730'}
],
rangeList: {
range: [
{to: 0, color: '#198038'},
{from: 0, to: 0, color: 'rgba(0, 0, 0, 0.87)'},
{from: 0, color: '#D12730'}
]
},
colorFunction: ''
},
showArrow: true

175
ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html

@ -0,0 +1,175 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="simpleGaugeWidgetConfigForm">
<tb-timewindow-config-panel *ngIf="displayTimewindowConfig"
[onlyHistoryTimewindow]="onlyHistoryTimewindow()"
formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<tb-datasources
[configMode]="basicMode"
hideDataKeyLabel
hideDataKeyColor
hideDataKeyUnits
hideDataKeyDecimals
formControlName="datasources">
</tb-datasources>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.gauge.gauge-appearance</div>
<tb-image-cards-select rowHeight="1:1"
[cols]="{columns: 4,
breakpoints: {
'lt-md': 2
}}"
label="{{ 'widgets.gauge.simple-gauge-type' | translate }}"
formControlName="gaugeType">
<tb-image-cards-select-option *ngFor="let layout of digitalGaugeLayouts"
[value]="layout"
[image]="digitalGaugeLayoutImageMap.get(layout)">
{{ digitalGaugeLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row space-between" *ngIf="simpleGaugeWidgetConfigForm.get('gaugeType').value === digitalGaugeType.donut">
<div class="fixed-title-width">{{ 'widgets.gauge.donut-start-angle' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
{{ 'widgets.gauge.min-and-max-value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="minMaxFont"
disabledLineHeight
[previewText]="previewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="minMaxColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue">
{{ 'widgets.gauge.value' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-unit-input
class="flex"
formControlName="units">
</tb-unit-input>
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div>
</mat-form-field>
<tb-font-settings formControlName="valueFont"
disabledLineHeight
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="valueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widgets.gauge.label' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="titleFont"
[previewText]="simpleGaugeWidgetConfigForm.get('title').value">
</tb-font-settings>
<tb-color-input formControlName="titleColor"
asBoxInput
colorClearButton>
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.gauge.default-color</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="defaultColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.gauge.gauge-bar-background</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="gaugeColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width">{{ 'widgets.gauge.bar-color' | translate }}</div>
<tb-color-settings formControlName="barColor"
rangeAdvancedMode
[minValue]="simpleGaugeWidgetConfigForm.get('minValue').value"
[maxValue]="simpleGaugeWidgetConfigForm.get('maxValue').value"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.background-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="backgroundColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-lt-md">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

276
ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.ts

@ -0,0 +1,276 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
WidgetConfig,
} from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import {
getTimewindowConfig,
setTimewindowConfig
} from '@home/components/widget/config/timewindow-config-panel.component';
import { formatValue, isUndefined } from '@core/utils';
import { Component } from '@angular/core';
import {
backwardCompatibilityFixedLevelColors,
defaultDigitalSimpleGaugeOptions,
digitalGaugeLayoutImages,
digitalGaugeLayouts,
digitalGaugeLayoutTranslations,
DigitalGaugeSettings,
DigitalGaugeType
} from '@home/components/widget/lib/digital-gauge.models';
import { ColorSettings, ColorType, constantColor } from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-digital-simple-gauge-basic-config',
templateUrl: './digital-simple-gauge-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigComponent {
public get displayTimewindowConfig(): boolean {
const datasources = this.simpleGaugeWidgetConfigForm.get('datasources').value;
return datasourcesHasAggregation(datasources);
}
public onlyHistoryTimewindow(): boolean {
const datasources = this.simpleGaugeWidgetConfigForm.get('datasources').value;
return datasourcesHasOnlyComparisonAggregation(datasources);
}
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
digitalGaugeType = DigitalGaugeType;
digitalGaugeLayouts = digitalGaugeLayouts;
digitalGaugeLayoutTranslationMap = digitalGaugeLayoutTranslations;
digitalGaugeLayoutImageMap = digitalGaugeLayoutImages;
simpleGaugeWidgetConfigForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this, true);
previewFn = this._valuePreviewFn.bind(this, false);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
protected fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.simpleGaugeWidgetConfigForm;
}
protected setupDefaults(configData: WidgetConfigComponentData) {
super.setupDefaults(configData);
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: DigitalGaugeSettings = {...defaultDigitalSimpleGaugeOptions, ...(configData.config.settings || {})};
if (!settings.barColor) {
settings.barColor = constantColor(settings.gaugeColor);
if (settings.fixedLevelColors?.length) {
settings.barColor.rangeList = {
advancedMode: settings.useFixedLevelColor,
range: null,
rangeAdvanced: backwardCompatibilityFixedLevelColors(settings.fixedLevelColors)
};
}
if (settings.levelColors?.length) {
settings.barColor.gradient = {
advancedMode: false,
gradient: settings.levelColors as string[],
gradientAdvanced: null
};
}
if (settings.useFixedLevelColor) {
settings.barColor.type = ColorType.range;
} else if (settings.levelColors.length) {
settings.barColor.type = ColorType.gradient;
}
}
this.simpleGaugeWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
gaugeType: [settings.gaugeType, []],
donutStartAngle: [settings.donutStartAngle, []],
showMinMax: [settings.showMinMax, []],
minValue: [settings.minValue, []],
maxValue: [settings.maxValue, []],
minMaxFont: [settings.minMaxFont, []],
minMaxColor: [settings.minMaxFont?.color, []],
showValue: [settings.showValue, []],
decimals: [configData.config.decimals, []],
units: [configData.config.units, []],
valueFont: [settings.valueFont, []],
valueColor: [settings.valueFont?.color, []],
showTitle: [settings.showTitle, []],
title: [settings.title, []],
titleFont: [settings.titleFont, []],
titleColor: [settings.titleFont?.color, []],
defaultColor: [settings.defaultColor, []],
gaugeColor: [settings.gaugeColor, []],
barColor: [settings.barColor, []],
cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []],
backgroundColor: [configData.config.backgroundColor, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig);
this.widgetConfig.config.datasources = config.datasources;
this.widgetConfig.config.actions = config.actions;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.backgroundColor = config.backgroundColor;
this.widgetConfig.config.settings.gaugeType = config.gaugeType;
this.widgetConfig.config.settings.donutStartAngle = config.donutStartAngle;
this.widgetConfig.config.settings.showMinMax = config.showMinMax;
this.widgetConfig.config.settings.minValue = config.minValue;
this.widgetConfig.config.settings.maxValue = config.maxValue;
this.widgetConfig.config.settings.minMaxFont = config.minMaxFont;
this.widgetConfig.config.settings.minMaxFont.color = config.minMaxColor;
this.widgetConfig.config.settings.showValue = config.showValue;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings.valueFont = config.valueFont;
this.widgetConfig.config.settings.valueFont.color = config.valueColor;
this.widgetConfig.config.settings.showTitle = config.showTitle;
this.widgetConfig.config.settings.title = config.title;
this.widgetConfig.config.settings.titleFont = config.titleFont;
this.widgetConfig.config.settings.titleFont.color = config.titleColor;
this.widgetConfig.config.settings.defaultColor = config.defaultColor;
this.widgetConfig.config.settings.gaugeColor = config.gaugeColor;
this.widgetConfig.config.settings.barColor = config.barColor;
const barColor: ColorSettings = config.barColor;
if (barColor.type === ColorType.range) {
this.widgetConfig.config.settings.useFixedLevelColor = true;
this.widgetConfig.config.settings.fixedLevelColors =
barColor.rangeList.advancedMode ? barColor.rangeList.rangeAdvanced : barColor.rangeList.range;
} else {
this.widgetConfig.config.settings.useFixedLevelColor = false;
}
if (barColor.gradient?.gradient?.length) {
this.widgetConfig.config.settings.levelColors = barColor.gradient.gradient;
}
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['gaugeType', 'showValue', 'showTitle', 'showMinMax'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const isDonut = this.simpleGaugeWidgetConfigForm.get('gaugeType').value === this.digitalGaugeType.donut;
if (isDonut) {
this.simpleGaugeWidgetConfigForm.get('donutStartAngle').enable();
this.simpleGaugeWidgetConfigForm.get('showMinMax').disable({emitEvent: false});
this.simpleGaugeWidgetConfigForm.get('minValue').enable();
this.simpleGaugeWidgetConfigForm.get('maxValue').enable();
this.simpleGaugeWidgetConfigForm.get('minMaxFont').disable();
this.simpleGaugeWidgetConfigForm.get('minMaxColor').disable();
} else {
this.simpleGaugeWidgetConfigForm.get('donutStartAngle').disable();
this.simpleGaugeWidgetConfigForm.get('showMinMax').enable({emitEvent: false});
if (this.simpleGaugeWidgetConfigForm.get('showMinMax').value) {
this.simpleGaugeWidgetConfigForm.get('minValue').enable();
this.simpleGaugeWidgetConfigForm.get('maxValue').enable();
this.simpleGaugeWidgetConfigForm.get('minMaxFont').enable();
this.simpleGaugeWidgetConfigForm.get('minMaxColor').enable();
} else {
this.simpleGaugeWidgetConfigForm.get('minValue').disable();
this.simpleGaugeWidgetConfigForm.get('maxValue').disable();
this.simpleGaugeWidgetConfigForm.get('minMaxFont').disable();
this.simpleGaugeWidgetConfigForm.get('minMaxColor').disable();
}
}
if (this.simpleGaugeWidgetConfigForm.get('showValue').value) {
this.simpleGaugeWidgetConfigForm.get('decimals').enable();
this.simpleGaugeWidgetConfigForm.get('units').enable();
this.simpleGaugeWidgetConfigForm.get('valueFont').enable();
this.simpleGaugeWidgetConfigForm.get('valueColor').enable();
} else {
this.simpleGaugeWidgetConfigForm.get('decimals').disable();
this.simpleGaugeWidgetConfigForm.get('units').disable();
this.simpleGaugeWidgetConfigForm.get('valueFont').disable();
this.simpleGaugeWidgetConfigForm.get('valueColor').disable();
}
if (this.simpleGaugeWidgetConfigForm.get('showTitle').value) {
this.simpleGaugeWidgetConfigForm.get('title').enable();
this.simpleGaugeWidgetConfigForm.get('titleFont').enable();
this.simpleGaugeWidgetConfigForm.get('titleColor').enable();
} else {
this.simpleGaugeWidgetConfigForm.get('title').disable();
this.simpleGaugeWidgetConfigForm.get('titleFont').disable();
this.simpleGaugeWidgetConfigForm.get('titleColor').disable();
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
private _valuePreviewFn(units: boolean): string {
return formatValue(22, 0, units ? this.simpleGaugeWidgetConfigForm.get('units').value : null, true);
}
}

28
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html

@ -99,18 +99,40 @@
[autoScale]="batteryLevelWidgetConfigForm.get('autoScaleValueSize').value"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="valueColor" settingsKey="{{'widgets.battery-level.value' | translate }}">
<tb-color-settings formControlName="valueColor"
rangeAdvancedMode
gradientAdvancedMode
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.battery-level.battery-level-color' | translate }}</div>
<tb-color-settings formControlName="batteryLevelColor" settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
<tb-color-settings formControlName="batteryLevelColor"
rangeAdvancedMode
gradientAdvancedMode
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.battery-level.battery-shape-color' | translate }}</div>
<tb-color-settings formControlName="batteryShapeColor" settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
<tb-color-settings formControlName="batteryShapeColor"
rangeAdvancedMode
gradientAdvancedMode
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">

10
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.ts

@ -22,6 +22,7 @@ import { BasicWidgetConfigComponent } from '@home/components/widget/config/widge
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
DataKey,
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
WidgetConfig,
@ -74,6 +75,15 @@ export class BatteryLevelBasicConfigComponent extends BasicWidgetConfigComponent
return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout);
}
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {

10
ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts

@ -101,10 +101,12 @@ export const countDefaultSettings = (alarmElseEntity: boolean): CountWidgetSetti
? {
color: 'rgba(0, 105, 92, 1)',
type: ColorType.range,
rangeList: [
{from: 0, to: 0, color: 'rgba(0, 105, 92, 1)'},
{from: 1, color: 'rgba(209, 39, 48, 1)'}
],
rangeList: {
range: [
{from: 0, to: 0, color: 'rgba(0, 105, 92, 1)'},
{from: 1, color: 'rgba(209, 39, 48, 1)'}
]
},
colorFunction: defaultColorFunction
}
: constantColor('rgba(241, 141, 23, 1)'),

88
ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts

@ -14,10 +14,17 @@
/// limitations under the License.
///
import { JsonSettingsSchema } from '@shared/models/widget.models';
import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge';
import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models';
import { FontSettings } from '@home/components/widget/lib/settings.models';
import {
AdvancedColorRange,
ColorSettings,
ValueSourceDataKeyType,
ValueSourceWithDataKey
} from '@shared/models/widget-settings.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isDefinedAndNotNull } from '@core/utils';
export interface AttributeSourceProperty {
valueSource: string;
@ -27,8 +34,8 @@ export interface AttributeSourceProperty {
}
export interface FixedLevelColors {
from?: AttributeSourceProperty;
to?: AttributeSourceProperty;
from?: ValueSourceWithDataKey | number;
to?: ValueSourceWithDataKey | number;
color: string;
}
@ -41,6 +48,33 @@ export type colorLevel = Array<string | ColorLevelSetting>;
export type attributesGaugeType = 'levelColors' | 'ticks';
export enum DigitalGaugeType {
arc = 'arc',
donut = 'donut',
horizontalBar = 'horizontalBar',
verticalBar = 'verticalBar'
}
export const digitalGaugeLayouts = Object.keys(DigitalGaugeType) as DigitalGaugeType[];
export const digitalGaugeLayoutTranslations = new Map<DigitalGaugeType, string>(
[
[DigitalGaugeType.arc, 'widgets.gauge.gauge-type-arc'],
[DigitalGaugeType.donut, 'widgets.gauge.gauge-type-donut'],
[DigitalGaugeType.horizontalBar, 'widgets.gauge.gauge-type-horizontal-bar'],
[DigitalGaugeType.verticalBar, 'widgets.gauge.gauge-type-vertical-bar']
]
);
export const digitalGaugeLayoutImages = new Map<DigitalGaugeType, string>(
[
[DigitalGaugeType.arc, 'assets/widget/simple-gauge/arc-layout.svg'],
[DigitalGaugeType.donut, 'assets/widget/simple-gauge/donut-layout.svg'],
[DigitalGaugeType.horizontalBar, 'assets/widget/simple-gauge/horizontal-bar-layout.svg'],
[DigitalGaugeType.verticalBar, 'assets/widget/simple-gauge/vertical-bar-layout.svg']
]
);
export interface DigitalGaugeSettings {
minValue?: number;
maxValue?: number;
@ -60,6 +94,9 @@ export interface DigitalGaugeSettings {
gaugeWidthScale?: number;
defaultColor?: string;
gaugeColor?: string;
barColor?: ColorSettings;
useFixedLevelColor?: boolean;
levelColors?: colorLevel;
fixedLevelColors?: FixedLevelColors[];
@ -75,8 +112,51 @@ export interface DigitalGaugeSettings {
hideValue?: boolean;
hideMinMax?: boolean;
showTicks?: boolean;
ticksValue?: AttributeSourceProperty[];
ticksValue?: ValueSourceWithDataKey[];
ticks?: number[];
colorTicks?: string;
tickWidth?: number;
}
export const defaultDigitalSimpleGaugeOptions: DigitalGaugeSettings = {
gaugeType: DigitalGaugeType.donut,
timestampFormat: 'yyyy-MM-dd HH:mm:ss',
};
export const backwardCompatibilityFixedLevelColors = (fixedLevelColors) => {
const valueSourceWithDataKey: AdvancedColorRange[] = [];
fixedLevelColors.forEach(fixedLevelColor => valueSourceWithDataKey.push({
from: {
type: fixedLevelColor?.from?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity,
value: fixedLevelColor?.from?.value || null,
entityAlias: fixedLevelColor?.from?.entityAlias || '',
entityKey: fixedLevelColor?.from?.attribute || '',
entityKeyType: DataKeyType.attribute
},
to: {
type: fixedLevelColor?.to?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity,
value: fixedLevelColor?.to?.value || null,
entityAlias: fixedLevelColor?.to?.entityAlias || '',
entityKey: fixedLevelColor?.to?.attribute || '',
entityKeyType: DataKeyType.attribute
},
color: fixedLevelColor.color
}) );
return valueSourceWithDataKey;
};
export const backwardCompatibilityTicks = (ticksValue) => {
const ticks: ValueSourceWithDataKey[] = [];
if (ticksValue?.length && isDefinedAndNotNull(ticksValue[0]?.valueSource)) {
ticksValue.forEach(tick => ticks.push({
type: tick?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity,
value: tick?.value || null,
entityAlias: tick?.entityAlias || '',
entityKey: tick?.attribute || '',
entityKeyType: DataKeyType.attribute
}) );
} else {
return ticksValue;
}
return ticks;
};

69
ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts

@ -18,27 +18,25 @@ import * as CanvasGauges from 'canvas-gauges';
import { WidgetContext } from '@home/models/widget-component.models';
import {
attributesGaugeType,
AttributeSourceProperty,
ColorLevelSetting,
DigitalGaugeSettings,
FixedLevelColors
} from '@home/components/widget/lib/digital-gauge.models';
import tinycolor from 'tinycolor2';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
import { isDefined, isDefinedAndNotNull, parseFunction, safeExecute } from '@core/utils';
import { prepareFontSettings } from '@home/components/widget/lib/settings.models';
import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
import { DatePipe } from '@angular/common';
import {
DataKey,
Datasource,
DatasourceData,
DatasourceType,
JsonSettingsSchema,
widgetType
} from '@shared/models/widget.models';
import { DataKey, Datasource, DatasourceData, DatasourceType, widgetType } from '@shared/models/widget.models';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { EMPTY, Observable } from 'rxjs';
import {
ColorProcessor,
ColorType,
ValueSourceDataKeyType,
ValueSourceWithDataKey
} from '@shared/models/widget-settings.models';
import GenericOptions = CanvasGauges.GenericOptions;
// @dynamic
@ -70,6 +68,7 @@ export class TbCanvasDigitalGauge {
this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75;
this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString();
this.localSettings.barColor = settings.barColor;
this.localSettings.useFixedLevelColor = settings.useFixedLevelColor || false;
if (!settings.useFixedLevelColor) {
@ -217,9 +216,9 @@ export class TbCanvasDigitalGauge {
throw new Error('Not valid entity aliase name ' + entityAlias);
}
const datasource = datasources.find((datasourceIteration) => {
return datasourceIteration.entityAliasId === entityAliasId;
});
const datasource = datasources.find((datasourceIteration) =>
datasourceIteration.entityAliasId === entityAliasId
);
const dataKey: DataKey = {
type: DataKeyType.attribute,
@ -230,9 +229,9 @@ export class TbCanvasDigitalGauge {
};
if (datasource) {
const findDataKey = datasource.dataKeys.find((dataKeyIteration) => {
return dataKeyIteration.name === attribute;
});
const findDataKey = datasource.dataKeys.find((dataKeyIteration) =>
dataKeyIteration.name === attribute
);
if (findDataKey) {
findDataKey.settings.push(settings);
@ -282,20 +281,26 @@ export class TbCanvasDigitalGauge {
color: this.localSettings.gaugeColor
});
function setLevelColor(levelSetting: AttributeSourceProperty, color: string) {
if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) {
function setLevelColor(levelSetting, color: string) {
if (levelSetting.type === ValueSourceDataKeyType.constant && isFinite(levelSetting.value)) {
predefineLevelColors.push({
value: levelSetting.value,
color
});
} else if (levelSetting.entityAlias && levelSetting.attribute) {
} else if (levelSetting.type === ValueSourceDataKeyType.latestKey || levelSetting.type === ValueSourceDataKeyType.entity) {
try {
levelColorsDatasource = TbCanvasDigitalGauge.generateDatasource(this.ctx, levelColorsDatasource,
levelSetting.entityAlias, levelSetting.attribute, {color, index: predefineLevelColors.length});
levelColorsDatasource = ColorProcessor.generateDatasource(
this.ctx, levelColorsDatasource, levelSetting, {color, index: predefineLevelColors.length}
);
} catch (e) {
return;
}
predefineLevelColors.push(null);
} else if (isFinite(levelSetting)) {
predefineLevelColors.push({
value: levelSetting,
color
});
}
}
@ -315,17 +320,18 @@ export class TbCanvasDigitalGauge {
return predefineLevelColors;
}
settingTicksSubscribe(options: AttributeSourceProperty[]): number[] {
settingTicksSubscribe(options: ValueSourceWithDataKey[]): number[] {
let ticksDatasource: Datasource[] = [];
const predefineTicks: number[] = [];
for (const tick of options) {
if (tick.valueSource === 'predefinedValue' && isFinite(tick.value)) {
if (tick.type === ValueSourceDataKeyType.constant && isFinite(tick.value)) {
predefineTicks.push(tick.value);
} else if (tick.entityAlias && tick.attribute) {
} else {
try {
ticksDatasource = TbCanvasDigitalGauge
.generateDatasource(this.ctx, ticksDatasource, tick.entityAlias, tick.attribute, predefineTicks.length);
ticksDatasource = ColorProcessor.generateDatasource(
this.ctx, ticksDatasource, tick, {index: predefineTicks.length}
);
} catch (e) {
continue;
}
@ -364,7 +370,7 @@ export class TbCanvasDigitalGauge {
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) {
for (const setting of keyData.dataKey.settings) {
for (const setting of keyData.dataKey.settings.dataKeySettings) {
switch (typeAttributes) {
case 'levelColors':
this.localSettings.levelColors[setting.index] = {
@ -373,7 +379,7 @@ export class TbCanvasDigitalGauge {
};
break;
case 'ticks':
this.localSettings.ticks[setting] = attrValue;
this.localSettings.ticks[setting.index] = attrValue;
break;
}
}
@ -409,6 +415,13 @@ export class TbCanvasDigitalGauge {
this.gauge._value = value;
}
this.gauge.value = value;
if (this.localSettings.barColor?.type === ColorType.function && isDefined(this.localSettings.barColor?.colorFunction)) {
this.localSettings.levelColors = [safeExecute(parseFunction(this.localSettings.barColor.colorFunction, ['value']), [value])];
this.updateSetting();
} else if (this.localSettings.barColor?.type === ColorType.constant && isDefinedAndNotNull(this.localSettings.barColor?.color)) {
this.localSettings.levelColors = [this.localSettings.barColor.color];
this.updateSetting();
}
} else if (this.localSettings.showTimestamp && this.gauge.timestamp !== timestamp) {
this.gauge.timestamp = timestamp;
}

21
ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts

@ -37,7 +37,8 @@ import {
getDataKey,
getSingleTsValue,
overlayStyle,
textStyle
textStyle,
updateGradientMinMaxValues
} from '@shared/models/widget-settings.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import {
@ -191,16 +192,20 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
this.showValue = this.settings.showValue;
this.autoScaleValueSize = this.showValue && this.settings.autoScaleValueSize;
this.valueStyle = textStyle(this.settings.valueFont);
this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor);
this.valueColor = ColorProcessor.fromSettings(updateGradientMinMaxValues(this.settings.valueColor, 0, 100), this.ctx);
this.batteryLevelColor = ColorProcessor.fromSettings(this.settings.batteryLevelColor);
this.batteryLevelColor = ColorProcessor.fromSettings(updateGradientMinMaxValues(this.settings.batteryLevelColor, 0, 100), this.ctx);
this.batteryShapeColor = ColorProcessor.fromSettings(this.settings.batteryShapeColor);
this.batteryShapeColor = ColorProcessor.fromSettings(updateGradientMinMaxValues(this.settings.batteryShapeColor, 0, 100), this.ctx);
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0;
this.valueColor.colorUpdated.subscribe(() => this.cd.markForCheck());
this.batteryLevelColor.colorUpdated.subscribe(() => this.cd.markForCheck());
this.batteryShapeColor.colorUpdated.subscribe(() => this.cd.markForCheck());
}
ngAfterViewInit() {
@ -218,6 +223,14 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
if (this.batteryBoxResize$) {
this.batteryBoxResize$.disconnect();
}
this.valueColor.colorUpdated.unsubscribe();
this.batteryLevelColor.colorUpdated.unsubscribe();
this.batteryShapeColor.colorUpdated.unsubscribe();
this.batteryLevelColor.destroy();
this.valueColor.destroy();
this.batteryShapeColor.destroy();
}
public onInit() {

24
ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts

@ -80,21 +80,25 @@ export const batteryLevelDefaultSettings: BatteryLevelWidgetSettings = {
batteryLevelColor: {
color: 'rgba(224, 224, 224, 1)',
type: ColorType.range,
rangeList: [
{from: null, to: 25, color: 'rgba(227, 71, 71, 1)'},
{from: 25, to: 50, color: 'rgba(246, 206, 67, 1)'},
{from: 50, to: null, color: 'rgba(92, 223, 144, 1)'}
],
rangeList: {
range: [
{from: null, to: 25, color: 'rgba(227, 71, 71, 1)'},
{from: 25, to: 50, color: 'rgba(246, 206, 67, 1)'},
{from: 50, to: null, color: 'rgba(92, 223, 144, 1)'}
]
},
colorFunction: defaultColorFunction
},
batteryShapeColor: {
color: 'rgba(224, 224, 224, 0.32)',
type: ColorType.range,
rangeList: [
{from: null, to: 25, color: 'rgba(227, 71, 71, 0.32)'},
{from: 25, to: 50, color: 'rgba(246, 206, 67, 0.32)'},
{from: 50, to: null, color: 'rgba(92, 223, 144, 0.32)'}
],
rangeList: {
range: [
{from: null, to: 25, color: 'rgba(227, 71, 71, 0.32)'},
{from: 25, to: 50, color: 'rgba(246, 206, 67, 0.32)'},
{from: 50, to: null, color: 'rgba(92, 223, 144, 0.32)'}
]
},
colorFunction: defaultColorFunction
},
background: {

14
ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts

@ -84,12 +84,14 @@ export const signalStrengthDefaultSettings: SignalStrengthWidgetSettings = {
activeBarsColor: {
color: 'rgba(92, 223, 144, 1)',
type: ColorType.range,
rangeList: [
{to: -85, color: 'rgba(227, 71, 71, 1)'},
{from: -85, to: -70, color: 'rgba(255, 122, 0, 1)'},
{from: -70, to: -55, color: 'rgba(246, 206, 67, 1)'},
{from: -55, color: 'rgba(92, 223, 144, 1)'}
],
rangeList: {
range: [
{to: -85, color: 'rgba(227, 71, 71, 1)'},
{from: -85, to: -70, color: 'rgba(255, 122, 0, 1)'},
{from: -70, to: -55, color: 'rgba(246, 206, 67, 1)'},
{from: -55, color: 'rgba(92, 223, 144, 1)'}
]
},
colorFunction: defaultColorFunction
},
noSignalRssiValue: -100,

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html

@ -209,7 +209,6 @@
<div cdkDrag class="tb-draggable" *ngFor="let thresholdControl of thresholdsFormArray().controls; trackBy: trackByThreshold;
let $index = index;">
<tb-flot-threshold [formControl]="thresholdControl"
[expanded]="thresholdControl.new"
[aliasController]="aliasController"
(removeThreshold)="removeThreshold($index)">
</tb-flot-threshold>

48
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html

@ -15,34 +15,22 @@
limitations under the License.
-->
<div class="tb-flot-threshold">
<mat-expansion-panel class="tb-settings tb-flot-threshold-settings" fxFlex [formGroup]="thresholdFormGroup" [(expanded)]="expanded">
<mat-expansion-panel-header>
<div class="tb-threshold-header">
<div class="tb-threshold-text">{{ thresholdText() }}</div>
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
<mat-divider vertical></mat-divider>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-value-source [aliasController]="aliasController" formControlName="valueSource"></tb-value-source>
<div class="tb-form-row space-between">
<div translate>widgets.chart.line-width</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="lineWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
<button *ngIf="!disabled" mat-icon-button
type="button"
(click)="removeThreshold.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
<div class="tb-form-row no-border no-padding column-lt-md" style="gap: 10px;" [formGroup]="thresholdFormGroup">
<tb-value-source style="flex: 1" [aliasController]="aliasController" formControlName="valueSource"></tb-value-source>
<div class="tb-form-row no-border no-padding" style="gap: 10px;">
<mat-form-field fxFlex.lt-md appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="lineWidth" type="number" min="0" placeholder="{{ 'widgets.chart.line-width' | translate }}">
</mat-form-field>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
<button *ngIf="!disabled" mat-icon-button
class="tb-box-button"
type="button"
(click)="removeThreshold.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>

78
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss

@ -1,78 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-flot-threshold {
display: flex;
flex-direction: row;
align-items: start;
gap: 4px;
.mat-expansion-panel.tb-settings.tb-flot-threshold-settings {
box-shadow: none;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.12);
.mat-expansion-panel-header {
height: 56px;
border-radius: 0;
display: flex;
flex-direction: row;
align-items: stretch;
.mat-content {
overflow: hidden;
}
.tb-threshold-header {
flex: 1;
display: flex;
flex-direction: row;
gap: 16px;
align-items: center;
padding-left: 16px;
overflow: hidden;
.mat-divider-vertical {
height: 100%;
}
}
.tb-threshold-text {
flex: 1;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.15px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.mat-expansion-indicator {
margin-right: 22px;
margin-left: 22px;
margin-top: 12px;
}
}
> .mat-expansion-panel-content {
> .mat-expansion-panel-body {
padding: 16px !important;
}
}
&.mat-expanded {
.mat-expansion-panel-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
}
}
> .mdc-icon-button {
margin-top: 4px;
color: rgba(0, 0, 0, 0.54);
}
}

28
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts

@ -26,15 +26,13 @@ import {
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { isNumber } from '@core/utils';
import { IAliasController } from '@core/api/widget-api.models';
import { TbFlotKeyThreshold } from '@home/components/widget/lib/flot-widget.models';
@Component({
selector: 'tb-flot-threshold',
templateUrl: './flot-threshold.component.html',
styleUrls: ['./flot-threshold.component.scss'],
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -49,9 +47,6 @@ export class FlotThresholdComponent extends PageComponent implements OnInit, Con
@Input()
disabled: boolean;
@Input()
expanded = false;
@Input()
aliasController: IAliasController;
@ -65,7 +60,6 @@ export class FlotThresholdComponent extends PageComponent implements OnInit, Con
public thresholdFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private fb: UntypedFormBuilder) {
super(store);
}
@ -110,26 +104,8 @@ export class FlotThresholdComponent extends PageComponent implements OnInit, Con
);
}
thresholdText(): string {
const value: ValueSourceProperty = this.thresholdFormGroup.get('valueSource').value;
return this.valueSourcePropertyText(value);
}
private valueSourcePropertyText(source?: ValueSourceProperty): string {
if (source) {
if (source.valueSource === 'predefinedValue') {
return `${isNumber(source.value) ? source.value : 0}`;
} else if (source.valueSource === 'entityAttribute') {
const alias = source.entityAlias || 'Undefined';
const key = source.attribute || 'Undefined';
return `${alias}.${key}`;
}
}
return 'Undefined';
}
private updateModel() {
const value: {valueSource: ValueSourceProperty, lineWidth: number, color: string} = this.thresholdFormGroup.value;
const value: {valueSource: ValueSourceProperty; lineWidth: number; color: string} = this.thresholdFormGroup.value;
this.modelValue = {
thresholdValueSource: value?.valueSource?.valueSource,
thresholdEntityAlias: value?.valueSource?.entityAlias,

56
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html

@ -0,0 +1,56 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-form-row no-padding no-border" style="gap: 0; width: 100%;" [formGroup]="advancedRangeLevelFormGroup">
<div class="range-container">
<div class="range-input range-input-fields">
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="range-text" translate>widgets.color.from</div>
<tb-value-source-data-key fxFlex
formControlName="from"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
</div>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="range-text" translate>widgets.color.to</div>
<tb-value-source-data-key fxFlex
formControlName="to"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
</div>
</div>
<tb-color-input asBoxInput
class="range-input"
required
colorClearButton
noBorder
formControlName="color">
</tb-color-input>
</div>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
class="tb-box-button"
(click)="removeAdvancedRange.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>

42
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.scss

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.range {
&-container {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
}
&-input {
padding: 7px;
&-fields {
display: flex;
flex-direction: column;
flex: 1;
gap: 12px;
border-right: 1px solid rgba(0, 0, 0, 0.12);;
}
}
&-text {
min-width: 50px;
font-size: 14px;
color: rgba(0, 0, 0, 0.38);
}
}
}

109
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts

@ -0,0 +1,109 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { IAliasController } from '@core/api/widget-api.models';
import { AdvancedColorRange } from '@shared/models/widget-settings.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({
selector: 'tb-advanced-range',
templateUrl: './advanced-range.component.html',
styleUrls: ['./advanced-range.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AdvancedRangeComponent),
multi: true
}
]
})
export class AdvancedRangeComponent extends PageComponent implements OnInit, ControlValueAccessor {
@Input()
disabled: boolean;
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Output()
removeAdvancedRange = new EventEmitter();
private modelValue: AdvancedColorRange;
private propagateChange = (v: any) => { };
public advancedRangeLevelFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
ngOnInit(): void {
this.advancedRangeLevelFormGroup = this.fb.group({
from: [null, []],
to: [null, []],
color: [null, [Validators.required]]
});
this.advancedRangeLevelFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.advancedRangeLevelFormGroup.disable({emitEvent: false});
} else {
this.advancedRangeLevelFormGroup.enable({emitEvent: false});
}
}
writeValue(value: AdvancedColorRange): void {
this.modelValue = value;
this.advancedRangeLevelFormGroup.patchValue(value, {emitEvent: false});
}
private updateModel() {
this.modelValue = this.advancedRangeLevelFormGroup.value;
this.propagateChange(this.modelValue);
}
}

123
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html

@ -15,36 +15,97 @@
limitations under the License.
-->
<div *ngIf="panelTitle" class="tb-form-panel-title">{{ panelTitle }}</div>
<div class="tb-color-ranges" [formGroup]="colorRangeListFormGroup">
<div class="tb-form-row no-padding no-border" [formGroup]="rangeFormGroup" *ngFor="let rangeFormGroup of rangeListFormGroups; trackBy: trackByRange; let $index = index;">
<div fxFlex fxLayout="row" fxLayoutGap="24px">
<div fxFlex fxLayout="row" fxLayoutGap="12px" fxLayoutAlign="start center">
<div class="tb-value-range-text" translate>widgets.color.from</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="from" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-value-range-text tb-value-range-text-to" translate>widgets.color.to</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="to" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
<button type="button"
mat-icon-button
class="tb-box-button"
(click)="removeRange($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
<ng-container [formGroup]="colorRangeListFormGroup">
<div fxLayout="row" fxLayoutAlign="start center" *ngIf="advancedMode; else basicMode">
<div fxFlex class="tb-form-panel-title" translate>widgets.color.settings-type</div>
<tb-toggle-select formControlName="advancedMode" fxFlex>
<tb-toggle-option [value]="false">
{{ 'widgets.color.basic-mode' | translate }}
</tb-toggle-option>
<tb-toggle-option [value]="true">
{{ 'widgets.color.advanced-mode' | translate}}
</tb-toggle-option>
</tb-toggle-select>
</div>
<ng-template #basicMode>
<div fxLayout="row" fxLayoutAlign="start center">
<div fxFlex *ngIf="panelTitle" class="tb-form-panel-title">{{ panelTitle }}</div>
</div>
</ng-template>
<div class="tb-color-ranges" [ngSwitch]="colorRangeListFormGroup.get('advancedMode').value">
<ng-template [ngSwitchCase]="false">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="rangeDrop($event, 'range')">
<div cdkDrag class="tb-form-row no-padding no-border tb-draggable"
[formGroup]="rangeFormGroup"
*ngFor="let rangeFormGroup of rangeListFormGroups; trackBy: trackByRange; let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<div class="range">
<div class="range-container">
<div class="tb-value-range-text" translate>widgets.color.from</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="from" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-value-range-text tb-value-range-text-to" translate>widgets.color.to</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="to" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
<button type="button"
mat-icon-button
class="tb-box-button"
(click)="removeRange($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
<button class="tb-box-button"
mat-icon-button
type="button"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
</div>
</ng-template>
<ng-template [ngSwitchCase]="true">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="rangeDrop($event, 'rangeAdvanced')">
<div cdkDrag class="tb-draggable tb-advanced-range-drag"
*ngFor="let advancedRangeControl of advancedRangeControls; trackBy: trackByAdvancedRange;let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-advanced-range [formControl]="advancedRangeControl"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
(removeAdvancedRange)="removeAdvancedRange($index)">
</tb-advanced-range>
<button class="tb-box-button"
mat-icon-button
type="button"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
</ng-template>
</div>
</div>
<button class="tb-add-color-range"
mat-stroked-button
(click)="addRange()">
<mat-icon>add</mat-icon>
</button>
<button class="tb-add-color-range"
mat-stroked-button
(click)="addRange()">
<mat-icon>add</mat-icon>
</button>
</ng-container>

36
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss

@ -13,28 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
display: block;
.mat-expansion-panel {
box-shadow: none;
&.fixed-color-level {
border: 1px groove rgba(0, 0, 0, .25);
.mat-expansion-panel-header {
padding: 0 24px 0 8px;
&.mat-expanded {
height: 48px;
}
}
}
}
}
.range {
display: flex;
flex: 1;
align-items: center;
flex-direction: row;
:host ::ng-deep {
.mat-expansion-panel {
&.fixed-color-level {
.mat-expansion-panel-body {
padding: 0 8px 8px;
}
&-container {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
gap: 12px;
padding: 8px;
margin-right: 12px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
}
}
}

123
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts

@ -18,21 +18,44 @@ import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormControl,
FormGroup,
NG_VALUE_ACCESSOR,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormGroup
UntypedFormGroup,
ValidationErrors
} from '@angular/forms';
import { ColorRange } from '@shared/models/widget-settings.models';
import {
AdvancedColorRange,
ColorRange,
ColorRangeSettings,
ValueSourceDataKeyType
} from '@shared/models/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { IAliasController } from '@core/api/widget-api.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
export function advancedRangeValidator(control: AbstractControl): ValidationErrors | null {
const range: AdvancedColorRange = control.value;
if (!range || !range.color) {
return {
advancedRange: true
};
}
return null;
}
@Component({
selector: 'tb-color-range-list',
templateUrl: './color-range-list.component.html',
styleUrls: ['color-settings-panel.component.scss'],
styleUrls: ['color-settings-panel.component.scss', 'color-range-list.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -52,19 +75,34 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
@Input()
panelTitle: string;
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
@coerceBoolean()
advancedMode = false;
modelValue: any;
colorRangeListFormGroup: UntypedFormGroup;
private destroy$ = new Subject<void>();
private propagateChange = null;
private propagateChange = (v: any) => { };
constructor(private fb: UntypedFormBuilder) {}
ngOnInit(): void {
this.colorRangeListFormGroup = this.fb.group({
rangeList: this.fb.array([])
advancedMode: [false],
range: this.fb.array([]),
rangeAdvanced: this.fb.array([])
});
this.colorRangeListFormGroup.valueChanges.pipe(
@ -89,8 +127,21 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
}
writeValue(value: any): void {
if (value && value?.length) {
value.forEach((r) => this.rangeListFormArray.push(this.colorRangeControl(r), {emitEvent: false}));
if (value) {
let rangeList: ColorRangeSettings = {};
if (isUndefined(value?.advancedMode) && value?.length) {
rangeList.advancedMode = false;
rangeList.range = value;
} else {
rangeList = deepClone(value);
}
this.colorRangeListFormGroup.get('advancedMode').patchValue(rangeList.advancedMode, {emitEvent: false});
if (isDefinedAndNotNull(rangeList?.range)) {
rangeList.range.forEach((r) => this.rangeListFormArray.push(this.colorRangeControl(r), {emitEvent: false}));
}
if (isDefinedAndNotNull(rangeList?.rangeAdvanced)) {
rangeList.rangeAdvanced.forEach((r) => this.advancedRangeFormArray.push(this.fb.control(r), {emitEvent: false}));
}
}
}
@ -103,7 +154,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
}
get rangeListFormArray(): UntypedFormArray {
return this.colorRangeListFormGroup.get('rangeList') as UntypedFormArray;
return this.colorRangeListFormGroup.get('range') as UntypedFormArray;
}
get rangeListFormGroups(): FormGroup[] {
@ -114,23 +165,65 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
return rangeControl;
}
public trackByAdvancedRange(index: number, advancedRangeControl: AbstractControl): any {
return advancedRangeControl;
}
public removeAdvancedRange(index: number) {
(this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray).removeAt(index);
}
get advancedRangeFormArray(): UntypedFormArray {
return this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray;
}
get advancedRangeControls(): FormControl[] {
return this.advancedRangeFormArray.controls as FormControl[];
}
removeRange(index: number) {
this.rangeListFormArray.removeAt(index);
this.colorRangeListFormGroup.markAsDirty();
setTimeout(() => {this.popover?.updatePosition();}, 0);
}
addRange() {
const newRange: ColorRange = {
color: 'rgba(0,0,0,0.87)'
rangeDrop(event: CdkDragDrop<string[]>, range: string) {
const rangeColorsArray = this.colorRangeListFormGroup.get(range) as UntypedFormArray;
const rangeColor = rangeColorsArray.at(event.previousIndex);
rangeColorsArray.removeAt(event.previousIndex);
rangeColorsArray.insert(event.currentIndex, rangeColor);
}
public addAdvancedRange() {
const advancedRange: AdvancedColorRange = {
from: {
type: ValueSourceDataKeyType.constant
},
to: {
type: ValueSourceDataKeyType.constant
},
color: null
};
this.rangeListFormArray.push(this.colorRangeControl(newRange));
this.colorRangeListFormGroup.markAsDirty();
setTimeout(() => {this.popover?.updatePosition();}, 0);
const advancedRangeColorsArray = this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray;
const advancedRangeColorControl = this.fb.control(advancedRange, [advancedRangeValidator]);
advancedRangeColorsArray.push(advancedRangeColorControl);
}
addRange() {
if (this.colorRangeListFormGroup.get('advancedMode').value) {
this.addAdvancedRange();
} else {
const newRange: ColorRange = {
color: 'rgba(0,0,0,0.87)'
};
this.rangeListFormArray.push(this.colorRangeControl(newRange));
this.colorRangeListFormGroup.markAsDirty();
setTimeout(() => {this.popover?.updatePosition();}, 0);
}
}
updateModel() {
this.propagateChange(this.colorRangeListFormGroup.get('rangeList').value);
this.propagateChange(this.colorRangeListFormGroup.value);
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html

@ -18,7 +18,7 @@
<div class="tb-color-settings-panel" [formGroup]="colorRangeFormGroup">
<div class="tb-color-settings-title" translate>widgets.color.color-settings</div>
<div class="tb-color-settings-panel-body">
<tb-color-range-list fxFlex class="tb-color-ranges-panel"
<tb-color-range-list class="tb-color-ranges-panel"
formControlName="rangeList"
[popover]="popover">
</tb-color-range-list>

21
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html

@ -33,10 +33,26 @@
</div>
<div class="tb-color-settings-panel-body" [fxShow]="colorSettingsFormGroup.get('type').value === colorType.constant">
</div>
<div class="tb-color-settings-panel-body" [fxShow]="colorSettingsFormGroup.get('type').value === colorType.gradient">
<tb-gradient class="tb-gradient-panel"
formControlName="gradient"
[minValue]="minValue"
[maxValue]="maxValue"
[advancedMode]="gradientAdvancedMode"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[popover]="popover">
</tb-gradient>
</div>
<div class="tb-color-settings-panel-body" [fxShow]="colorSettingsFormGroup.get('type').value === colorType.range">
<tb-color-range-list fxFlex class="tb-color-ranges-panel"
<tb-color-range-list class="tb-color-ranges-panel"
formControlName="rangeList"
panelTitle="{{ 'widgets.color.value-range' | translate }}"
[advancedMode]="rangeAdvancedMode"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[popover]="popover">
</tb-color-range-list>
</div>
@ -50,7 +66,8 @@
color="primary"
type="button"
[matMenuTriggerFor]="settingsSourcesMenu" [matMenuTriggerData]="{menuWidth: copySettingsButton._elementRef.nativeElement.clientWidth}">
{{ 'widgets.color.copy-color-settings-from' | translate }}
<span fxHide.xs>{{ 'widgets.color.copy-color-settings-from' | translate }}</span>
<span fxHide.gt-xs>{{ 'widgets.color.copy-from' | translate }}</span>
</button>
<mat-menu #settingsSourcesMenu="matMenu">
<ng-template matMenuContent let-menuWidth="menuWidth">

21
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss

@ -16,8 +16,10 @@
@import '../../../../../../../../scss/constants';
.tb-color-settings-panel {
width: 500px;
height: 470px;
width: 700px;
max-width: 90vw;
min-height: 300px;
max-height: 90vh;
display: flex;
flex-direction: column;
gap: 16px;
@ -31,6 +33,12 @@
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-gradient-panel {
flex: 1;
gap: 16px;
display: flex;
flex-direction: column;
}
.tb-color-ranges-panel {
flex: 1;
min-height: 0;
@ -82,3 +90,12 @@
align-items: flex-end;
}
}
.tb-advanced-range-drag {
display: flex;
flex-direction: row;
align-items: center;
tb-advanced-range {
flex: 1;
}
}

38
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts

@ -16,7 +16,12 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { ColorSettings, ColorType, colorTypeTranslations } from '@shared/models/widget-settings.models';
import {
ColorSettings,
ColorType,
colorTypeTranslations,
defaultGradient
} from '@shared/models/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
@ -24,6 +29,10 @@ import { AppState } from '@core/core.state';
import { deepClone } from '@core/utils';
import { WidgetService } from '@core/http/widget.service';
import { ColorSettingsComponent } from '@home/components/widget/lib/settings/common/color-settings.component';
import { IAliasController } from '@core/api/widget-api.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({
selector: 'tb-color-settings-panel',
@ -46,6 +55,29 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
@Output()
colorSettingsApplied = new EventEmitter<ColorSettings>();
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
@coerceBoolean()
rangeAdvancedMode = false;
@Input()
@coerceBoolean()
gradientAdvancedMode = false;
@Input()
minValue: string;
@Input()
maxValue: string;
colorType = ColorType;
colorTypes = Object.keys(ColorType) as ColorType[];
@ -67,6 +99,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
{
type: [this.colorSettings?.type || ColorType.constant, []],
color: [this.colorSettings?.color, []],
gradient: [this.colorSettings?.gradient || defaultGradient(+this.minValue, +this.maxValue), []],
rangeList: [this.colorSettings?.rangeList, []],
colorFunction: [this.colorSettings?.colorFunction, []]
}
@ -82,8 +115,9 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
this.colorSettingsFormGroup.patchValue({
type: this.colorSettings.type,
color: this.colorSettings.color,
gradient: this.colorSettings.gradient || null,
colorFunction: this.colorSettings.colorFunction,
rangeList: this.colorSettings.rangeList || []
rangeList: this.colorSettings.rangeList || null
}, {emitEvent: false});
this.colorSettingsFormGroup.markAsDirty();
}

77
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts

@ -25,12 +25,17 @@ import {
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ColorSettings, ColorType, ComponentStyle } from '@shared/models/widget-settings.models';
import { ColorRange, ColorSettings, ColorType, ComponentStyle } from '@shared/models/widget-settings.models';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import {
ColorSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/color-settings-panel.component';
import { IAliasController } from '@core/api/widget-api.models';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Injectable()
export class ColorSettingsComponentService {
@ -79,13 +84,36 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
@Input()
settingsKey: string;
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
@coerceBoolean()
rangeAdvancedMode = false;
@Input()
@coerceBoolean()
gradientAdvancedMode = false;
@Input()
minValue: string;
@Input()
maxValue: string;
colorType = ColorType;
modelValue: ColorSettings;
colorStyle: ComponentStyle = {};
private propagateChange = null;
private propagateChange = (v: any) => { };
constructor(private popoverService: TbPopoverService,
private renderer: Renderer2,
@ -113,8 +141,14 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
}
writeValue(value: ColorSettings): void {
this.modelValue = value;
this.updateColorStyle();
if (value) {
this.modelValue = value;
if (isDefinedAndNotNull(this.modelValue.rangeList) && !isDefinedAndNotNull(this.modelValue.rangeList?.advancedMode)) {
const range = deepClone(this.modelValue.rangeList) as ColorRange[];
this.modelValue.rangeList = deepClone({advancedMode: false, range});
}
this.updateColorStyle();
}
}
openColorSettingsPopup($event: Event, matButton: MatButton) {
@ -127,7 +161,14 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
} else {
const ctx: any = {
colorSettings: this.modelValue,
settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this)
settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this),
aliasController: this.aliasController,
dataKeyCallbacks: this.dataKeyCallbacks,
datasource: this.datasource,
rangeAdvancedMode: this.rangeAdvancedMode,
gradientAdvancedMode: this.gradientAdvancedMode,
minValue: this.minValue,
maxValue: this.maxValue
};
const colorSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ColorSettingsPanelComponent, 'left', true, null,
@ -147,18 +188,32 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
private updateColorStyle() {
if (!this.disabled && this.modelValue) {
let colors: string[] = [this.modelValue.color];
if (this.modelValue.type === ColorType.range && this.modelValue.rangeList?.length) {
const rangeColors = this.modelValue.rangeList.slice(0, Math.min(2, this.modelValue.rangeList.length)).map(r => r.color);
const rangeList = this.modelValue.rangeList;
if (this.modelValue.type === ColorType.range && (rangeList?.range?.length || rangeList?.rangeAdvanced?.length)) {
let rangeColors: Array<string>;
if (rangeList?.advancedMode) {
rangeColors = rangeList.rangeAdvanced.slice(0, Math.min(2, rangeList.rangeAdvanced.length)).map(r => r.color);
} else {
rangeColors = rangeList.range.slice(0, Math.min(2, rangeList.range.length)).map(r => r.color);
}
colors = colors.concat(rangeColors);
} else if (this.modelValue.type === ColorType.gradient) {
colors = this.modelValue.gradient?.advancedMode ?
this.modelValue.gradient.gradientAdvanced.map(color => color.color) :
this.modelValue.gradient.gradient;
}
if (colors.length === 1) {
this.colorStyle = {backgroundColor: colors[0]};
} else {
const gradientValues: string[] = [];
const step = 100 / colors.length;
for (let i = 0; i < colors.length; i++) {
gradientValues.push(`${colors[i]} ${step*i}%`);
gradientValues.push(`${colors[i]} ${step*(i+1)}%`);
if (this.modelValue.type === ColorType.gradient) {
gradientValues.push(...colors);
} else {
const step = 100 / colors.length;
for (let i = 0; i < colors.length; i++) {
gradientValues.push(`${colors[i]} ${step*i}%`);
gradientValues.push(`${colors[i]} ${step*(i+1)}%`);
}
}
this.colorStyle = {background: `linear-gradient(90deg, ${gradientValues.join(', ')})`};
}

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss

@ -28,8 +28,8 @@
padding-right: 0;
.mat-mdc-form-field-infix {
padding-top: 0;
padding-bottom: 6px;
padding-top: 0 !important;
padding-bottom: 6px !important;
.mdc-evolution-chip-set .mdc-evolution-chip {
margin: 0;

208
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html

@ -0,0 +1,208 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="gradientFormGroup">
<div fxLayout="row" fxLayoutAlign="start center" *ngIf="advancedMode">
<div fxFlex class="tb-form-panel-title" fxHide.xs translate>widgets.color.gradient-type</div>
<tb-toggle-select formControlName="advancedMode" fxFlex>
<tb-toggle-option [value]="false">
{{ 'widgets.color.basic-mode' | translate }}
</tb-toggle-option>
<tb-toggle-option [value]="true">
{{ 'widgets.color.advanced-mode' | translate}}
</tb-toggle-option>
</tb-toggle-select>
</div>
<div class="gradient-preview">
<div [style]="linearGradient" class="gradient-background" [innerHtml]="(generatePointers | safe: 'html')">
</div>
</div>
<div class="gradient-settings" [ngSwitch]="gradientFormGroup.get('advancedMode').value">
<ng-template [ngSwitchCase]="false">
<div class="gradient" formGroupName="gradient">
<div class="gradient-container gradient-container-start">
<div translate>widgets.color.gradient-start</div>
<div class="start-gradient-container">
<div class="tb-form-row no-border no-padding" fxHide.xs [fxHide]="!minValue?.toString()?.length">
<div class="gradient-text" translate>widgets.color.start-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" disabled [(ngModel)]="minValue" [ngModelOptions]="{standalone: true}" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<tb-color-input asBoxInput
formControlName="start">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-colors-gradient" *ngIf="gradientListFormGroups.length">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="gradientDrop($event)">
<div cdkDrag class="tb-form-row no-padding no-border tb-draggable"
[formGroup]="gradientFormGroup"
*ngFor="let gradientFormGroup of gradientListFormGroups; trackBy: trackByGradient; let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<div class="gradient">
<div class="gradient-container">
<div translate>widgets.color.gradient-color</div>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
<button type="button"
mat-icon-button
class="tb-box-button"
(click)="removeGradient($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
<button class="tb-box-button"
fxHide.lt-lg
mat-icon-button
type="button"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
</div>
</div>
<button class="tb-add-gradient"
mat-stroked-button
(click)="addGradient()">
<mat-icon>add</mat-icon>
</button>
<div class="gradient" formGroupName="gradient">
<div class="gradient-container gradient-container-end">
<div translate>widgets.color.gradient-end</div>
<div class="end-gradient-container">
<div class="tb-form-row no-border no-padding" fxHide.xs [fxHide]="!maxValue?.toString()?.length">
<div class="gradient-text" translate>widgets.color.end-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" disabled [(ngModel)]="maxValue" [ngModelOptions]="{standalone: true}" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<tb-color-input asBoxInput
formControlName="end">
</tb-color-input>
</div>
</div>
</div>
</ng-template>
<ng-template [ngSwitchCase]="true">
<div class="gradient" formGroupName="gradientAdvanced">
<div class="gradient-container gradient-container-start" formGroupName="start">
<div class="tb-gradient-text">
<span fxHide.xs translate>widgets.color.gradient-start</span>
<span fxHide fxShow.xs translate>widgets.color.gradient-start-min</span>
</div>
<div class="tb-form-row no-border no-padding start-gradient-container start-gradient-container-advanced">
<tb-value-source-data-key fxFlex
formControlName="source"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-colors-gradient" *ngIf="advancedGradientListFormGroups.length">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="gradientDrop($event, true)">
<div cdkDrag class="tb-form-row no-padding no-border tb-draggable"
[formGroup]="gradientFormGroup"
*ngFor="let gradientFormGroup of advancedGradientListFormGroups; trackBy: trackByGradient; let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<div class="gradient">
<div class="gradient-container">
<div class="tb-gradient-text">
<span fxHide.xs translate>widgets.color.gradient-color</span>
<span fxHide fxShow.xs translate>widgets.color.gradient-color-min</span>
</div>
<div class="list-gradient-container list-gradient-container-advanced">
<tb-value-source-data-key fxFlex
formControlName="source"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<button type="button"
mat-icon-button
class="tb-box-button"
(click)="removeGradient($index, true)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
<button class="tb-box-button"
fxHide.lt-lg
mat-icon-button
type="button"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
</div>
</div>
<button class="tb-add-gradient"
mat-stroked-button
(click)="addGradient(true)">
<mat-icon>add</mat-icon>
</button>
<div class="gradient" formGroupName="gradientAdvanced">
<div class="gradient-container gradient-container-end" formGroupName="end">
<div class="tb-gradient-text">
<span fxHide.xs translate>widgets.color.gradient-end</span>
<span fxHide fxShow.xs translate>widgets.color.gradient-end-min</span>
</div>
<div class="tb-form-row no-border no-padding end-gradient-container end-gradient-container-advanced">
<tb-value-source-data-key fxFlex
formControlName="source"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
</div>
</ng-template>
</div>
</ng-container>

145
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss

@ -0,0 +1,145 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../../../../scss/constants";
:host {
overflow: auto;
height: 100%;
max-height: 420px;
.gradient {
display: flex;
flex: 1;
align-items: center;
flex-direction: row;
&-container {
display: flex;
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 12px;
padding: 8px;
margin-right: 12px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
&-start, &-end {
margin-right: 92px;
@media #{$mat-lt-lg} {
margin-right: 52px;
}
}
.tb-gradient-text {
width: 140px;
@media #{$mat-xs} {
width: 50px;
}
}
}
.start-gradient-container, .end-gradient-container, .list-gradient-container {
display: flex;
gap: 8px;
.gradient-text {
font-size: 14px;
color: rgba(0, 0, 0, 0.38);
}
&-advanced {
flex: 1;
@media #{$mat-xs} {
align-items: center;
}
}
}
}
.gradient-preview {
width: 100%;
padding: 40px 12px 0;
.gradient-background {
position: relative;
height: 56px;
border-radius: 8px;
}
}
.gradient-settings {
flex: 1;
gap: 16px;
display: flex;
flex-direction: column;
}
.tb-add-gradient {
margin-right: 92px;
@media #{$mat-lt-lg} {
margin-right: 52px;
}
}
}
::ng-deep {
.gradient-background {
.pointer {
position: absolute;
width: 8px;
height: 60px;
border-radius: 6px;
top: -2px;
border: 2px solid white;
-webkit-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.75);
-moz-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.75);
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.75);
&.start {
left: 1px;
}
&.end {
right: 1px;
}
&-value {
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: center;
width: 33px;
height: 24px;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.06);
&-text {
font-size: 14px;
font-weight: 500;
}
&:after {
content: "";
bottom: -8px;
position: absolute;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid rgba(0, 0, 0, 0.06);
}
}
}
}
}

266
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts

@ -0,0 +1,266 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormGroup,
NG_VALUE_ACCESSOR,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormGroup
} from '@angular/forms';
import { AdvancedGradient, ColorGradientSettings, ValueSourceDataKeyType } from '@shared/models/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { IAliasController } from '@core/api/widget-api.models';
import { DomSanitizer } from '@angular/platform-browser';
import { coerceBoolean } from '@shared/decorators/coercion';
import { isDefinedAndNotNull } from '@core/utils';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({
selector: 'tb-gradient',
templateUrl: './gradient.component.html',
styleUrls: ['color-settings-panel.component.scss', 'gradient.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => GradientComponent),
multi: true
}
]
})
export class GradientComponent implements OnInit, ControlValueAccessor, OnDestroy {
@ViewChild('gradient') gradient: ElementRef;
@Input()
disabled: boolean;
@Input()
popover: TbPopoverComponent;
@Input()
panelTitle: string;
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
minValue: string;
@Input()
maxValue: string;
@Input()
@coerceBoolean()
advancedMode = true;
modelValue: any;
gradientFormGroup: UntypedFormGroup;
private destroy$ = new Subject<void>();
private propagateChange = (v: any) => { };
constructor(private fb: UntypedFormBuilder,
private sanitizer: DomSanitizer) {}
ngOnInit(): void {
this.gradientFormGroup = this.fb.group({
advancedMode: [false],
gradient: this.fb.group({
start: ['rgba(0, 255, 0, 1)'],
gradientList: this.fb.array([]),
end: ['rgba(255, 0, 0, 1)']
}),
gradientAdvanced: this.fb.group({
start: this.fb.group({
source: [{type: ValueSourceDataKeyType.constant}],
color: ['rgba(0, 255, 0, 1)']
}),
gradientList: this.fb.array([]),
end: this.fb.group({
source: [{type: ValueSourceDataKeyType.constant}],
color: ['rgba(255, 0, 0, 1)']
})
})
});
this.gradientFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(() => this.updateModel());
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: ColorGradientSettings): void {
if (isDefinedAndNotNull(value)) {
this.gradientFormGroup.get('advancedMode').patchValue(value.advancedMode, {emitEvent: false});
if (value?.gradient?.length) {
this.gradientFormGroup.get('gradient').get('start').patchValue(value.gradient[0], {emitEvent: false});
this.gradientFormGroup.get('gradient').get('end').patchValue(value.gradient[value.gradient.length - 1], {emitEvent: false});
value.gradient.slice(1, -1).forEach(r => this.gradientListFormArray.push(this.colorGradientControl(r), {emitEvent: false}));
}
if (value?.gradientAdvanced?.length) {
this.gradientFormGroup.get('gradientAdvanced').get('start').patchValue(value.gradientAdvanced[0], {emitEvent: false});
this.gradientFormGroup.get('gradientAdvanced').get('end').patchValue(
value.gradientAdvanced[value.gradientAdvanced.length - 1], {emitEvent: false}
);
value.gradientAdvanced.slice(1, -1).forEach(
r => this.advancedGradientListFormArray.push(this.advancedGradientControl(r), {emitEvent: false})
);
}
}
}
get generatePointers() {
if (this.gradientFormGroup.get('advancedMode').value) {
const shift = 100 / (this.advancedGradientListFormArray.value.length + 1);
return `<div class="pointer start"></div>` +
this.advancedGradientListFormArray.value.map((v, i) => this.pointer(shift * (i + 1), i+1, null, true)).join('') +
`<div class="pointer end"></div>`;
} else {
const point = (+this.maxValue - +this.minValue) / (this.gradientListFormArray.value.length + 1);
const shift = 100 / (this.gradientListFormArray.value.length + 1);
const min = isDefinedAndNotNull(this.minValue) ? this.minValue : 0;
const max = isDefinedAndNotNull(this.maxValue) ? this.maxValue : 100;
return `<div class="pointer start"><div class="pointer-value"><span class="pointer-value-text">${min}</span></div></div>` +
this.gradientListFormArray.value.map((v, i) => this.pointer(shift * (i + 1), i+1, point)).join('') +
`<div class="pointer end"><div class="pointer-value"><span class="pointer-value-text">${max}</span></div></div>`;
}
}
pointer(shift: number, index?: number, value?: number, advanced = false) {
if (advanced) {
return `<div class="pointer" style="left: ${shift}%"></div>`;
} else {
return `<div class="pointer" style="left: ${shift}%">` +
`<div class="pointer-value"><span class="pointer-value-text">${Math.floor(+this.minValue + (value * index))}</span></div></div>`;
}
}
get linearGradient() {
const gradient = this.gradientFormGroup.get('advancedMode').value ?
[this.gradientFormGroup.value.gradientAdvanced.start.color,
...this.gradientFormGroup.value.gradientAdvanced.gradientList.map(item => item.color),
this.gradientFormGroup.value.gradientAdvanced.end.color].join(', ') :
[this.gradientFormGroup.value.gradient.start,
...this.gradientFormGroup.value.gradient.gradientList.map(item => item.color),
this.gradientFormGroup.value.gradient.end].join(', ');
return this.sanitizer.bypassSecurityTrustStyle(`background-image: linear-gradient(90deg, ${gradient})`);
}
private colorGradientControl(gradient: string): UntypedFormGroup {
return this.fb.group({
color: [gradient, []]
});
}
get gradientListFormArray(): UntypedFormArray {
return this.gradientFormGroup.get('gradient.gradientList') as UntypedFormArray;
}
get gradientListFormGroups(): FormGroup[] {
return this.gradientListFormArray.controls as FormGroup[];
}
private advancedGradientControl(gradient: AdvancedGradient): UntypedFormGroup {
return this.fb.group({
source: [gradient.source, []],
color: [gradient.color, []]
});
}
get advancedGradientListFormArray(): UntypedFormArray {
return this.gradientFormGroup.get('gradientAdvanced.gradientList') as UntypedFormArray;
}
get advancedGradientListFormGroups(): FormGroup[] {
return this.advancedGradientListFormArray.controls as FormGroup[];
}
trackByGradient(index: number, gradientControl: AbstractControl): any {
return gradientControl;
}
removeGradient(index: number, advanced = false) {
if (advanced) {
this.advancedGradientListFormArray.removeAt(index);
} else {
this.gradientListFormArray.removeAt(index);
}
this.gradientFormGroup.markAsDirty();
setTimeout(() => {this.popover?.updatePosition();}, 0);
}
gradientDrop(event: CdkDragDrop<string[]>, advanced = false) {
const gradientColorsArray = advanced ? this.advancedGradientListFormArray : this.gradientListFormArray;
const gradientColor = gradientColorsArray.at(event.previousIndex);
gradientColorsArray.removeAt(event.previousIndex);
gradientColorsArray.insert(event.currentIndex, gradientColor);
}
addGradient(advanced = false) {
if (advanced) {
this.advancedGradientListFormArray.push(
this.advancedGradientControl({source: {type: ValueSourceDataKeyType.constant}, color: 'rgba(0,0,0,0.87)'})
);
} else {
this.gradientListFormArray.push(this.colorGradientControl('rgba(0,0,0,0.87)'));
}
this.gradientFormGroup.markAsDirty();
setTimeout(() => {this.popover?.updatePosition();}, 0);
}
updateModel() {
this.propagateChange(
{
advancedMode: this.gradientFormGroup.value.advancedMode,
gradient: [this.gradientFormGroup.value.gradient.start,
...this.gradientFormGroup.value.gradient.gradientList.map(item => item.color),
this.gradientFormGroup.value.gradient.end],
gradientAdvanced: [this.gradientFormGroup.value.gradientAdvanced.start,
...this.gradientFormGroup.value.gradientAdvanced.gradientList,
this.gradientFormGroup.value.gradientAdvanced.end]
}
);
}
}

77
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.html

@ -0,0 +1,77 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div [formGroup]="valueSourceFormGroup" class="tb-value-source tb-form-row no-padding no-border">
<div class="tb-source-field">
<mat-form-field class="tb-type-field" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of valueSourceDataKeyTypes" [value]="type">
{{ valueSourceDataKeyTypeTranslation.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<tb-entity-alias-input
class="tb-entity-alias-field"
*ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.entity"
[aliasController]="aliasController"
required
formControlName="entityAlias">
</tb-entity-alias-input>
</div>
<div class="tb-key-value-field">
<mat-form-field *ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.constant"
appearance="outline" class="tb-constant-field number" subscriptSizing="dynamic"
[class.tb-suffix-absolute]="!valueSourceFormGroup.get('value').value">
<input required matInput formControlName="value" type="number" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'widgets.value-source.value-required' | translate"
*ngIf="valueSourceFormGroup.get('value').hasError('required')
&& valueSourceFormGroup.get('value').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
<tb-data-key-input
*ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.latestKey"
required
requiredText="widgets.value-source.key-required"
[datasourceType]="datasource?.type"
[entityAliasId]="datasource?.entityAliasId"
[deviceId]="datasource?.deviceId"
[aliasController]="aliasController"
[dataKeyType]="datasource?.type === datasourceType.function ? dataKeyType.function : null"
[dataKeyTypes]="[dataKeyType.attribute, dataKeyType.timeseries]"
[callbacks]="dataKeyCallbacks"
[editable]="false"
[formControl]="latestKeyFormControl">
</tb-data-key-input>
<tb-data-key-input
*ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.entity"
required
requiredText="widgets.value-source.entity-key-required"
[datasourceType]="datasourceType.entity"
[aliasController]="aliasController"
[entityAlias]="valueSourceFormGroup.get('entityAlias').value"
[dataKeyTypes]="[dataKeyType.attribute, dataKeyType.timeseries]"
[callbacks]="dataKeyCallbacks"
[editable]="false"
[formControl]="entityKeyFormControl">
</tb-data-key-input>
</div>
</div>

38
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.scss

@ -13,28 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
display: block;
.mat-expansion-panel {
box-shadow: none;
&.tick-value {
border: 1px groove rgba(0, 0, 0, .25);
.mat-expansion-panel-header {
padding: 0 24px 0 8px;
&.mat-expanded {
height: 48px;
}
}
.tb-value-source {
display: flex;
gap: 12px;
}
.tb-source-field {
display: flex;
flex: 1;
gap: 12px;
.tb-type-field, .tb-entity-alias-field {
flex: 1;
}
}
}
:host ::ng-deep {
.mat-expansion-panel {
&.tick-value {
.mat-expansion-panel-body {
padding: 0 8px 8px;
}
.tb-key-value-field, .tb-constant-field {
display: flex;
flex: 1;
}
::ng-deep {
.tb-data-key-input {
flex: 1;
}
}
}

191
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts

@ -0,0 +1,191 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { IAliasController } from '@core/api/widget-api.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import {
ValueSourceDataKeyType,
ValueSourceDataKeyTypes,
ValueSourceDataKeyTypeTranslation
} from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-value-source-data-key',
templateUrl: './value-source-data-key.component.html',
styleUrls: ['value-source-data-key.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ValueSourceDataKeyComponent),
multi: true
}
]
})
export class ValueSourceDataKeyComponent extends PageComponent implements OnInit, ControlValueAccessor {
dataKeyType = DataKeyType;
datasourceType = DatasourceType;
valueSourceDataKeyType = ValueSourceDataKeyType;
valueSourceDataKeyTypes = ValueSourceDataKeyTypes;
valueSourceDataKeyTypeTranslation = ValueSourceDataKeyTypeTranslation;
@Input()
disabled: boolean;
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
valueSourceFormGroup: UntypedFormGroup;
modelValue;
latestKeyFormControl: UntypedFormControl;
entityKeyFormControl: UntypedFormControl;
private propagateChange = (_val: any) => {};
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef) {
super(store);
}
ngOnInit(): void {
this.valueSourceFormGroup = this.fb.group({
type: [ValueSourceDataKeyType.constant, []],
value: [null, [Validators.required]],
entityAlias: [null, [Validators.required]]
});
this.latestKeyFormControl = this.fb.control(null, [Validators.required]);
this.entityKeyFormControl = this.fb.control(null, [Validators.required]);
this.valueSourceFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
this.latestKeyFormControl.valueChanges.subscribe(
() => this.updateModel()
);
this.entityKeyFormControl.valueChanges.subscribe(
() => this.updateModel()
);
this.valueSourceFormGroup.get('type').valueChanges.subscribe(() => {
this.updateValidators();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.valueSourceFormGroup.disable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false});
} else {
this.valueSourceFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value): void {
this.modelValue = value;
this.valueSourceFormGroup.patchValue(
{
type: value.type,
value: value.value,
entityAlias: value.entityAlias
}, {emitEvent: false}
);
if (value.type === ValueSourceDataKeyType.latestKey) {
this.latestKeyFormControl.patchValue({
type: value.latestKeyType,
name: value.latestKey
}, {emitEvent: false});
} else if (value.type === ValueSourceDataKeyType.entity) {
this.entityKeyFormControl.patchValue({
type: value.entityKeyType,
name: value.entityKey
}, {emitEvent: false});
}
this.updateValidators();
this.cd.markForCheck();
}
private updateModel() {
const value = this.valueSourceFormGroup.value;
this.modelValue.type = value.type;
this.modelValue.value = value.value;
this.modelValue.entityAlias = value.entityAlias;
if (value.type === ValueSourceDataKeyType.latestKey) {
const latestKey: DataKey = this.latestKeyFormControl.value;
this.modelValue.latestKey = latestKey?.name;
this.modelValue.latestKeyType = (latestKey?.type as any);
} else if (value.type === ValueSourceDataKeyType.entity) {
const entityKey: DataKey = this.entityKeyFormControl.value;
this.modelValue.entityKey = entityKey?.name;
this.modelValue.entityKeyType = (entityKey?.type as any);
}
this.propagateChange(this.modelValue);
}
private updateValidators(): void {
const type: ValueSourceDataKeyType = this.valueSourceFormGroup.get('type').value;
if (type === ValueSourceDataKeyType.constant) {
this.valueSourceFormGroup.get('value').enable({emitEvent: false});
this.valueSourceFormGroup.get('entityAlias').disable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false});
} else if (type === ValueSourceDataKeyType.latestKey) {
this.valueSourceFormGroup.get('value').disable({emitEvent: false});
this.valueSourceFormGroup.get('entityAlias').disable({emitEvent: false});
this.latestKeyFormControl.enable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false});
} else if (type === ValueSourceDataKeyType.entity) {
this.valueSourceFormGroup.get('value').disable({emitEvent: false});
this.valueSourceFormGroup.get('entityAlias').enable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.enable({emitEvent: false});
}
}
}

114
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html

@ -15,65 +15,59 @@
limitations under the License.
-->
<ng-container [formGroup]="valueSourceFormGroup">
<div style="display: flex;">
<tb-toggle-select formControlName="valueSource" style="width: 300px;" selectMediaBreakpoint="xs">
<tb-toggle-option [value]="'predefinedValue'">
<div class="tb-form-row no-border no-padding column-xs" style="gap: 10px;" [formGroup]="valueSourceFormGroup">
<mat-form-field fxFlex="33" fxFlex.xs appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="valueSource">
<mat-option [value]="'predefinedValue'">
{{ 'widgets.value-source.predefined-value' | translate }}
</tb-toggle-option>
<tb-toggle-option [value]="'entityAttribute'">
</mat-option>
<mat-option [value]="'entityAttribute'">
{{ 'widgets.value-source.entity-attribute' | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<div [fxShow]="valueSourceFormGroup.get('valueSource').value === 'predefinedValue'" class="tb-form-row space-between">
<div translate>widgets.value-source.value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="value" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div [fxShow]="valueSourceFormGroup.get('valueSource').value === 'entityAttribute'" class="tb-form-row space-between column-xs">
<div translate>widgets.value-source.source-entity-alias</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<input matInput type="text" placeholder="{{ 'widget-config.set' | translate }}"
#entityAliasInput
formControlName="entityAlias"
[matAutocomplete]="entityAliasAutocomplete">
<button *ngIf="valueSourceFormGroup.get('entityAlias').value && !valueSourceFormGroup.get('entityAlias').disabled"
type="button"
matSuffix mat-icon-button aria-label="Clear"
(click)="clearEntityAlias()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete
class="tb-autocomplete"
#entityAliasAutocomplete="matAutocomplete">
<mat-option *ngFor="let entityAlias of filteredEntityAliases | async" [value]="entityAlias">
<span [innerHTML]="entityAlias | highlight:aliasSearchText"></span>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<div [fxShow]="valueSourceFormGroup.get('valueSource').value === 'entityAttribute'" class="tb-form-row space-between column-xs">
<div translate>widgets.value-source.source-entity-attribute</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<input matInput type="text" placeholder="{{ 'widget-config.set' | translate }}"
#keyInput
formControlName="attribute"
[matAutocomplete]="keyAutocomplete">
<button *ngIf="valueSourceFormGroup.get('attribute').value && !valueSourceFormGroup.get('attribute').disabled"
type="button"
matSuffix mat-icon-button aria-label="Clear"
(click)="clearKey()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete
class="tb-autocomplete"
#keyAutocomplete="matAutocomplete">
<mat-option *ngFor="let key of filteredKeys | async" [value]="key">
<span [innerHTML]="key | highlight:keySearchText"></span>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
</ng-container>
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field [fxShow]="valueSourceFormGroup.get('valueSource').value === 'predefinedValue'"
appearance="outline"
class="number flex"
subscriptSizing="dynamic">
<input matInput formControlName="value" type="number" placeholder="{{ 'widgets.value-source.value' | translate }}">
</mat-form-field>
<mat-form-field [fxShow]="valueSourceFormGroup.get('valueSource').value === 'entityAttribute'"
appearance="outline"
class="flex"
subscriptSizing="dynamic">
<input matInput type="text" placeholder="{{ entityAliasPlaceholder | translate }}"
#entityAliasInput
formControlName="entityAlias"
[matAutocomplete]="entityAliasAutocomplete">
<button *ngIf="valueSourceFormGroup.get('entityAlias').value && !valueSourceFormGroup.get('entityAlias').disabled"
type="button"
matSuffix mat-icon-button aria-label="Clear"
(click)="clearEntityAlias()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete class="tb-autocomplete" #entityAliasAutocomplete="matAutocomplete">
<mat-option *ngFor="let entityAlias of filteredEntityAliases | async" [value]="entityAlias">
<span [innerHTML]="entityAlias | highlight:aliasSearchText"></span>
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic" [fxShow]="valueSourceFormGroup.get('valueSource').value === 'entityAttribute'">
<input matInput type="text" placeholder="{{ entityAttributePlaceholder | translate }}"
#keyInput
formControlName="attribute"
[matAutocomplete]="keyAutocomplete">
<button *ngIf="valueSourceFormGroup.get('attribute').value && !valueSourceFormGroup.get('attribute').disabled"
type="button"
matSuffix mat-icon-button aria-label="Clear"
(click)="clearKey()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete class="tb-autocomplete" #keyAutocomplete="matAutocomplete">
<mat-option *ngFor="let key of filteredKeys | async" [value]="key">
<span [innerHTML]="key | highlight:keySearchText"></span>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>

11
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts

@ -19,7 +19,6 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFor
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { IAliasController } from '@core/api/widget-api.models';
import { Observable, of } from 'rxjs';
import { catchError, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators';
@ -60,9 +59,15 @@ export class ValueSourceComponent extends PageComponent implements OnInit, Contr
@Input()
aliasController: IAliasController;
@Input()
entityAliasPlaceholder = 'widgets.value-source.source-entity-alias';
@Input()
entityAttributePlaceholder = 'widgets.value-source.source-entity-attribute';
private modelValue: ValueSourceProperty;
private propagateChange = null;
private propagateChange = (v: any) => { };
public valueSourceFormGroup: UntypedFormGroup;
@ -78,7 +83,6 @@ export class ValueSourceComponent extends PageComponent implements OnInit, Contr
private entityAliasList: Array<string> = [];
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private entityService: EntityService,
private fb: UntypedFormBuilder) {
super(store);
@ -247,5 +251,4 @@ export class ValueSourceComponent extends PageComponent implements OnInit, Contr
this.valueSourceFormGroup.get('attribute').updateValueAndValidity({emitEvent: false});
this.valueSourceFormGroup.get('value').updateValueAndValidity({emitEvent: false});
}
}

18
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts

@ -49,7 +49,8 @@ import {
import { ColorRangeListComponent } from '@home/components/widget/lib/settings/common/color-range-list.component';
import { ColorRangePanelComponent } from '@home/components/widget/lib/settings/common/color-range-panel.component';
import {
ColorRangeSettingsComponent, ColorRangeSettingsComponentService
ColorRangeSettingsComponent,
ColorRangeSettingsComponentService
} from '@home/components/widget/lib/settings/common/color-range-settings.component';
import {
GetValueActionSettingsComponent
@ -149,6 +150,11 @@ import {
StatusWidgetStateSettingsComponent
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component';
import { AdvancedRangeComponent } from '@home/components/widget/lib/settings/common/advanced-range.component';
import { GradientComponent } from '@home/components/widget/lib/settings/common/gradient.component';
import {
ValueSourceDataKeyComponent
} from '@home/components/widget/lib/settings/common/value-source-data-key.component';
@NgModule({
declarations: [
@ -167,6 +173,7 @@ import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueSourceComponent,
ValueSourceDataKeyComponent,
LegendConfigComponent,
WidgetFontComponent,
CountWidgetSettingsComponent,
@ -205,7 +212,9 @@ import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/
TimeSeriesChartGridSettingsComponent,
StatusWidgetStateSettingsComponent,
DataKeyInputComponent,
EntityAliasInputComponent
EntityAliasInputComponent,
AdvancedRangeComponent,
GradientComponent
],
imports: [
CommonModule,
@ -228,6 +237,7 @@ import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueSourceComponent,
ValueSourceDataKeyComponent,
LegendConfigComponent,
WidgetFontComponent,
CountWidgetSettingsComponent,
@ -266,7 +276,9 @@ import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/
TimeSeriesChartGridSettingsComponent,
StatusWidgetStateSettingsComponent,
DataKeyInputComponent,
EntityAliasInputComponent
EntityAliasInputComponent,
AdvancedRangeComponent,
GradientComponent
],
providers: [
ColorSettingsComponentService,

596
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html

@ -15,368 +15,298 @@
limitations under the License.
-->
<section class="tb-widget-settings" [formGroup]="digitalGaugeWidgetSettingsForm" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.common-settings</legend>
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.min-value</mat-label>
<input matInput type="number" formControlName="minValue">
<ng-container [formGroup]="digitalGaugeWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.gauge.gauge-appearance</div>
<tb-image-cards-select rowHeight="1:1"
[cols]="{columns: 4,
breakpoints: {
'lt-md': 2
}}"
label="{{ 'widgets.gauge.simple-gauge-type' | translate }}"
formControlName="gaugeType">
<tb-image-cards-select-option *ngFor="let layout of digitalGaugeLayouts"
[value]="layout"
[image]="digitalGaugeLayoutImageMap.get(layout)">
{{ digitalGaugeLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row space-between" *ngIf="digitalGaugeWidgetSettingsForm.get('gaugeType').value === digitalGaugeType.donut">
<div class="fixed-title-width">{{ 'widgets.gauge.donut-start-angle' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.max-value</mat-label>
<input matInput type="number" formControlName="maxValue">
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
{{ 'widgets.gauge.min-and-max-value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="minMaxFont"
disabledLineHeight
[previewText]="previewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="minMaxColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue">
{{ 'widgets.gauge.value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="valueFont"
disabledLineHeight
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="valueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widgets.gauge.label' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="titleFont"
[previewText]="digitalGaugeWidgetSettingsForm.get('title').value">
</tb-font-settings>
<tb-color-input formControlName="titleColor"
asBoxInput
colorClearButton>
</tb-color-input>
</div>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.gauge.unit-title-and-timestamp-settings</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showUnitTitle">
{{ 'widgets.gauge.show-unit-title' | translate }}
</mat-slide-toggle>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="unitTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTimestamp">
{{ 'widgets.gauge.show-timestamp' | translate }}
</mat-slide-toggle>
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="timestampFormat"></tb-date-format-select>
</div>
<div class="tb-form-row space-between column-xs">
<div class="fixed-title-width">{{ 'widgets.gauge.font' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="labelFont"
disabledLineHeight
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="labelColor">
</tb-color-input>
</div>
</div>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.gauge.bar-settings</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width">{{ 'widgets.gauge.relative-bar-width' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="gaugeWidthScale" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</section>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.gauge-type</mat-label>
<mat-select formControlName="gaugeType">
<mat-option [value]="'arc'">
{{ 'widgets.gauge.gauge-type-arc' | translate }}
</mat-option>
<mat-option [value]="'donut'">
{{ 'widgets.gauge.gauge-type-donut' | translate }}
</mat-option>
<mat-option [value]="'horizontalBar'">
{{ 'widgets.gauge.gauge-type-horizontal-bar' | translate }}
</mat-option>
<mat-option [value]="'verticalBar'">
{{ 'widgets.gauge.gauge-type-vertical-bar' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field [fxShow]="digitalGaugeWidgetSettingsForm.get('gaugeType').value === 'donut'" fxFlex class="mat-block">
<mat-label translate>widgets.gauge.donut-start-angle</mat-label>
<input matInput type="number" formControlName="donutStartAngle">
</mat-form-field>
<tb-color-input
formControlName="defaultColor"
icon="format_color_fill"
label="{{ 'widgets.gauge.default-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.bar-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.relative-bar-width</mat-label>
<input matInput type="number" min="0" formControlName="gaugeWidthScale">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.neon-glow-brightness</mat-label>
<input matInput type="number" min="0" max="100" formControlName="neonGlowBrightness">
</mat-form-field>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.stripes-thickness</mat-label>
<input matInput type="number" min="0" formControlName="dashThickness">
</div>
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{'widgets.gauge.neon-glow-brightness-hint' | translate}}" class="fixed-title-width">{{ 'widgets.gauge.neon-glow-brightness' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" max="100" formControlName="neonGlowBrightness" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{'widgets.gauge.stripes-thickness-hint' | translate}}" class="fixed-title-width">{{ 'widgets.gauge.stripes-thickness' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="dashThickness" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-checkbox fxFlex formControlName="roundedLineCap">
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="roundedLineCap">
{{ 'widgets.gauge.rounded-line-cap' | translate }}
</mat-checkbox>
</section>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.bar-color-settings</legend>
<tb-color-input
formControlName="gaugeColor"
icon="format_color_fill"
label="{{ 'widgets.gauge.bar-background-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<mat-slide-toggle class="slide-block" formControlName="useFixedLevelColor">
{{ 'widgets.gauge.use-precise-level-color-values' | translate }}
</mat-slide-toggle>
<fieldset [fxShow]="!digitalGaugeWidgetSettingsForm.get('useFixedLevelColor').value" class="fields-group">
<legend class="group-title" translate>widgets.gauge.bar-colors</legend>
<div fxLayout="column">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="levelColorDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let levelColorControl of levelColorsFormArray().controls; trackBy: trackByLevelColor;
let $index = index; last as isLast;"
fxLayout="row" fxLayoutAlign="start center" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<div cdkDragHandle class="tb-drag-handle" style="margin-right: 10px;">
<mat-icon class="tb-mat-20">drag_handle</mat-icon>
</div>
<tb-color-input fxFlex
[formControl]="levelColorControl"
icon="format_color_fill"
label="{{ 'widgets.gauge.color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<button mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeLevelColor($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<div *ngIf="!levelColorsFormArray().controls.length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">widgets.gauge.no-bar-colors</span>
</div>
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addLevelColor()">
<span translate>widgets.gauge.add-bar-color</span>
</button>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width">{{ 'widgets.gauge.default-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="defaultColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width">{{ 'widgets.gauge.gauge-bar-background' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="gaugeColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width">{{ 'widgets.gauge.bar-color' | translate }}</div>
<tb-color-settings formControlName="barColor"
rangeAdvancedMode
[minValue]="digitalGaugeWidgetSettingsForm.get('minValue').value"
[maxValue]="digitalGaugeWidgetSettingsForm.get('maxValue').value"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="digitalGaugeWidgetSettingsForm.get('showTicks').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTicks" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.gauge.ticks' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between column-xs">
<div class="fixed-title-width">{{ 'widgets.gauge.tick-width-and-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="tickWidth" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input asBoxInput
colorClearButton
formControlName="colorTicks">
</tb-color-input>
</div>
</div>
</fieldset>
<fieldset [fxShow]="digitalGaugeWidgetSettingsForm.get('useFixedLevelColor').value" class="fields-group">
<legend class="group-title" translate>widgets.gauge.fixed-level-colors</legend>
<div fxLayout="column">
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.gauge.tick-values</div>
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="fixedLevelColorDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let fixedLevelColorControl of fixedLevelColorFormArray().controls; trackBy: trackByFixedLevelColor;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-fixed-color-level [formControl]="fixedLevelColorControl"
[expanded]="fixedLevelColorControl.new"
[aliasController]="aliasController"
(removeFixedColorLevel)="removeFixedLevelColor($index)">
</tb-fixed-color-level>
(cdkDropListDropped)="tickValueDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let tickValueControl of tickValuesFormArray().controls; trackBy: trackByTickValue;
let $index = index; last as isLast;" fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-tick-value [formControl]="tickValueControl"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
(removeTickValue)="removeTickValue($index)">
</tb-tick-value>
</div>
</div>
<div *ngIf="!fixedLevelColorFormArray().controls.length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">widgets.gauge.no-bar-colors</span>
<div *ngIf="!tickValuesFormArray().controls.length">
<span translate fxLayoutAlign="center center" class="tb-prompt">widgets.gauge.no-tick-values</span>
</div>
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
<button mat-stroked-button color="primary"
type="button"
(click)="addFixedLevelColor()">
<span translate>widgets.gauge.add-bar-color</span>
[disabled]="!digitalGaugeWidgetSettingsForm.get('showTicks').value"
(click)="addTickValue()">
<span translate>widgets.gauge.add-tick-value</span>
</button>
</div>
</div>
</fieldset>
</fieldset>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.gauge.gauge-title-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="digitalGaugeWidgetSettingsForm.get('showTitle').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showTitle" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.gauge.show-gauge-title' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.gauge-title</mat-label>
<input matInput formControlName="title">
</mat-form-field>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.gauge-title-font</legend>
<tb-widget-font formControlName="titleFont"></tb-widget-font>
</fieldset>
</section>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.unit-title-and-timestamp-settings</legend>
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-slide-toggle fxFlex formControlName="showUnitTitle">
{{ 'widgets.gauge.show-unit-title' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.unit-title</mat-label>
<input matInput formControlName="unitTitle">
</mat-form-field>
</section>
<section fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-slide-toggle fxFlex formControlName="showTimestamp">
{{ 'widgets.gauge.show-timestamp' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.timestamp-format</mat-label>
<input matInput formControlName="timestampFormat">
</mat-form-field>
</section>
<mat-expansion-panel class="tb-settings" [expanded]="digitalGaugeWidgetSettingsForm.get('showUnitTitle').value ||
digitalGaugeWidgetSettingsForm.get('showTimestamp').value">
<mat-expansion-panel-header>
<mat-panel-description fxLayoutAlign="end" translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.label-font</legend>
<tb-widget-font formControlName="labelFont"></tb-widget-font>
</fieldset>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.gauge.value-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="digitalGaugeWidgetSettingsForm.get('showValue').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showValue" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.gauge.show-value' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.value-font</legend>
<tb-widget-font formControlName="valueFont"></tb-widget-font>
</fieldset>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.gauge.min-max-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="digitalGaugeWidgetSettingsForm.get('showMinMax').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showMinMax" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.gauge.show-min-max' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.min-max-font</legend>
<tb-widget-font formControlName="minMaxFont"></tb-widget-font>
</fieldset>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.gauge.ticks-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="digitalGaugeWidgetSettingsForm.get('showTicks').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showTicks" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.gauge.show-ticks' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.tick-width</mat-label>
<input matInput type="number" min="0" formControlName="tickWidth">
</mat-form-field>
<tb-color-input
formControlName="colorTicks"
icon="format_color_fill"
label="{{ 'widgets.gauge.tick-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.tick-values</legend>
<div fxLayout="column">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="tickValueDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let tickValueControl of tickValuesFormArray().controls; trackBy: trackByTickValue;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
</div>
<tb-tick-value [formControl]="tickValueControl"
[expanded]="tickValueControl.new"
[aliasController]="aliasController"
(removeTickValue)="removeTickValue($index)">
</tb-tick-value>
</div>
</div>
<div *ngIf="!tickValuesFormArray().controls.length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">widgets.gauge.no-tick-values</span>
</div>
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addTickValue()">
<span translate>widgets.gauge.add-tick-value</span>
</button>
</div>
</div>
</fieldset>
</section>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.gauge.animation-settings</legend>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="digitalGaugeWidgetSettingsForm.get('animation').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="animation" (click)="$event.stopPropagation()"
<mat-slide-toggle class="mat-slide" formControlName="animation" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.gauge.enable-animation' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.animation-duration</mat-label>
<input matInput type="number" min="0" formControlName="animationDuration">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gauge.animation-rule</mat-label>
<mat-select formControlName="animationRule">
<mat-option [value]="'linear'">
{{ 'widgets.gauge.animation-linear' | translate }}
</mat-option>
<mat-option [value]="'quad'">
{{ 'widgets.gauge.animation-quad' | translate }}
</mat-option>
<mat-option [value]="'quint'">
{{ 'widgets.gauge.animation-quint' | translate }}
</mat-option>
<mat-option [value]="'cycle'">
{{ 'widgets.gauge.animation-cycle' | translate }}
</mat-option>
<mat-option [value]="'bounce'">
{{ 'widgets.gauge.animation-bounce' | translate }}
</mat-option>
<mat-option [value]="'elastic'">
{{ 'widgets.gauge.animation-elastic' | translate }}
</mat-option>
<mat-option [value]="'dequad'">
{{ 'widgets.gauge.animation-dequad' | translate }}
</mat-option>
<mat-option [value]="'dequint'">
{{ 'widgets.gauge.animation-dequint' | translate }}
</mat-option>
<mat-option [value]="'decycle'">
{{ 'widgets.gauge.animation-decycle' | translate }}
</mat-option>
<mat-option [value]="'debounce'">
{{ 'widgets.gauge.animation-debounce' | translate }}
</mat-option>
<mat-option [value]="'delastic'">
{{ 'widgets.gauge.animation-delastic' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</section>
<div class="tb-form-row space-between">
<div translate>widgets.gauge.animation-duration-rule</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="animationDuration" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="animationRule">
<mat-option [value]="'linear'">
{{ 'widgets.gauge.animation-linear' | translate }}
</mat-option>
<mat-option [value]="'quad'">
{{ 'widgets.gauge.animation-quad' | translate }}
</mat-option>
<mat-option [value]="'quint'">
{{ 'widgets.gauge.animation-quint' | translate }}
</mat-option>
<mat-option [value]="'cycle'">
{{ 'widgets.gauge.animation-cycle' | translate }}
</mat-option>
<mat-option [value]="'bounce'">
{{ 'widgets.gauge.animation-bounce' | translate }}
</mat-option>
<mat-option [value]="'elastic'">
{{ 'widgets.gauge.animation-elastic' | translate }}
</mat-option>
<mat-option [value]="'dequad'">
{{ 'widgets.gauge.animation-dequad' | translate }}
</mat-option>
<mat-option [value]="'dequint'">
{{ 'widgets.gauge.animation-dequint' | translate }}
</mat-option>
<mat-option [value]="'decycle'">
{{ 'widgets.gauge.animation-decycle' | translate }}
</mat-option>
<mat-option [value]="'debounce'">
{{ 'widgets.gauge.animation-debounce' | translate }}
</mat-option>
<mat-option [value]="'delastic'">
{{ 'widgets.gauge.animation-delastic' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
</section>
</div>
</ng-container>

284
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { Component } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
@ -22,10 +22,22 @@ import { AppState } from '@core/core.state';
import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import {
FixedColorLevel,
fixedColorLevelValidator
} from '@home/components/widget/lib/settings/gauge/fixed-color-level.component';
import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component';
backwardCompatibilityFixedLevelColors,
backwardCompatibilityTicks,
digitalGaugeLayoutImages,
digitalGaugeLayouts,
digitalGaugeLayoutTranslations,
DigitalGaugeType
} from '@home/components/widget/lib/digital-gauge.models';
import { formatValue } from '@core/utils';
import {
ColorSettings,
ColorType,
constantColor,
simpleDateFormat,
ValueSourceDataKeyType,
ValueSourceWithDataKey
} from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-digital-gauge-widget-settings',
@ -34,8 +46,26 @@ import { ValueSourceProperty } from '@home/components/widget/lib/settings/common
})
export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent {
digitalGaugeType = DigitalGaugeType;
digitalGaugeLayouts = digitalGaugeLayouts;
digitalGaugeLayoutTranslationMap = digitalGaugeLayoutTranslations;
digitalGaugeLayoutImageMap = digitalGaugeLayoutImages;
digitalGaugeWidgetSettingsForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this, true);
previewFn = this._valuePreviewFn.bind(this, false);
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
constructor(protected store: Store<AppState>,
protected fb: UntypedFormBuilder) {
super(store);
@ -107,63 +137,101 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
}
protected onSettingsSet(settings: WidgetSettings) {
if (!settings.barColor) {
settings.barColor = constantColor(settings.gaugeColor);
if (settings.fixedLevelColors.length) {
settings.barColor.rangeList = {
advancedMode: settings.useFixedLevelColor,
range: null,
rangeAdvanced: backwardCompatibilityFixedLevelColors(settings.fixedLevelColors)
};
}
if (settings.levelColors.length) {
settings.barColor.gradient = {
advancedMode: false,
gradient: settings.levelColors,
gradientAdvanced: null
};
}
if (settings.useFixedLevelColor) {
settings.barColor.type = ColorType.range;
} else if (settings.levelColors.length) {
settings.barColor.type = ColorType.gradient;
}
}
this.digitalGaugeWidgetSettingsForm = this.fb.group({
// Common gauge settings
minValue: [settings.minValue, []],
maxValue: [settings.maxValue, []],
gaugeType: [settings.gaugeType, []],
donutStartAngle: [settings.donutStartAngle, []],
defaultColor: [settings.defaultColor, []],
// Gauge bar settings
gaugeWidthScale: [settings.gaugeWidthScale, [Validators.min(0)]],
neonGlowBrightness: [settings.neonGlowBrightness, [Validators.min(0), Validators.max(100)]],
dashThickness: [settings.dashThickness, [Validators.min(0)]],
roundedLineCap: [settings.roundedLineCap, []],
showMinMax: [settings.showMinMax, []],
minValue: [settings.minValue, []],
maxValue: [settings.maxValue, []],
minMaxFont: [settings.minMaxFont, []],
minMaxColor: [settings.minMaxFont.color, []],
// Gauge bar colors settings
gaugeColor: [settings.gaugeColor, []],
useFixedLevelColor: [settings.useFixedLevelColor, []],
levelColors: this.prepareLevelColorFormArray(settings.levelColors),
fixedLevelColors: this.prepareFixedLevelColorFormArray(settings.fixedLevelColors),
showValue: [settings.showValue, []],
valueFont: [settings.valueFont, []],
valueColor: [settings.valueFont.color, []],
// Title settings
showTitle: [settings.showTitle, []],
title: [settings.title, []],
titleFont: [settings.titleFont, []],
titleColor: [settings.titleFont.color, []],
// Unit title/timestamp settings
showUnitTitle: [settings.showUnitTitle, []],
unitTitle: [settings.unitTitle, []],
showTimestamp: [settings.showTimestamp, []],
timestampFormat: [settings.timestampFormat, []],
timestampFormat: [simpleDateFormat(settings.timestampFormat), []],
labelFont: [settings.labelFont, []],
labelColor: [settings.labelFont.color, []],
// Value settings
showValue: [settings.showValue, []],
valueFont: [settings.valueFont, []],
gaugeWidthScale: [settings.gaugeWidthScale, [Validators.min(0)]],
neonGlowBrightness: [settings.neonGlowBrightness, [Validators.min(0), Validators.max(100)]],
dashThickness: [settings.dashThickness, [Validators.min(0)]],
roundedLineCap: [settings.roundedLineCap, []],
// Min/max labels settings
showMinMax: [settings.showMinMax, []],
minMaxFont: [settings.minMaxFont, []],
defaultColor: [settings.defaultColor, []],
gaugeColor: [settings.gaugeColor, []],
barColor: [settings.barColor],
// Ticks settings
showTicks: [settings.showTicks, []],
tickWidth: [settings.tickWidth, [Validators.min(0)]],
colorTicks: [settings.colorTicks, []],
ticksValue: this.prepareTicksValueFormArray(settings.ticksValue),
ticksValue: this.prepareTicksValueFormArray(backwardCompatibilityTicks(settings.ticksValue)),
// Animation settings
animation: [settings.animation, []],
animationDuration: [settings.animationDuration, [Validators.min(0)]],
animationRule: [settings.animationRule, []]
});
}
protected prepareOutputSettings(settings) {
const barColor: ColorSettings = this.digitalGaugeWidgetSettingsForm.get('barColor').value;
if (barColor.type === ColorType.range) {
settings.useFixedLevelColor = true;
settings.fixedLevelColors = barColor.rangeList.advancedMode ? barColor.rangeList.rangeAdvanced : barColor.rangeList.range;
} else {
settings.useFixedLevelColor = false;
}
if (barColor.gradient?.gradient?.length) {
settings.levelColors = barColor.gradient.gradient;
}
settings.barColor = this.digitalGaugeWidgetSettingsForm.get('barColor').value;
settings.timestampFormat = this.digitalGaugeWidgetSettingsForm.get('timestampFormat').value.format;
settings.minMaxFont.color = this.digitalGaugeWidgetSettingsForm.get('minMaxColor').value;
settings.valueFont.color = this.digitalGaugeWidgetSettingsForm.get('valueColor').value;
settings.titleFont.color = this.digitalGaugeWidgetSettingsForm.get('titleColor').value;
settings.labelFont.color = this.digitalGaugeWidgetSettingsForm.get('labelColor').value;
return settings;
}
protected validatorTriggers(): string[] {
return ['gaugeType', 'showTitle', 'showUnitTitle', 'showValue', 'showMinMax', 'showTimestamp', 'useFixedLevelColor', 'showTicks', 'animation'];
return ['gaugeType', 'showTitle', 'showUnitTitle', 'showValue', 'showMinMax', 'showTimestamp', 'showTicks', 'animation'];
}
protected updateValidators(emitEvent: boolean) {
@ -173,21 +241,43 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
const showValue: boolean = this.digitalGaugeWidgetSettingsForm.get('showValue').value;
const showMinMax: boolean = this.digitalGaugeWidgetSettingsForm.get('showMinMax').value;
const showTimestamp: boolean = this.digitalGaugeWidgetSettingsForm.get('showTimestamp').value;
const useFixedLevelColor: boolean = this.digitalGaugeWidgetSettingsForm.get('useFixedLevelColor').value;
const showTicks: boolean = this.digitalGaugeWidgetSettingsForm.get('showTicks').value;
const animation: boolean = this.digitalGaugeWidgetSettingsForm.get('animation').value;
if (gaugeType === 'donut') {
this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').enable();
this.digitalGaugeWidgetSettingsForm.get('showMinMax').disable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('minValue').enable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('maxValue').enable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable({emitEvent: false});
} else {
this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').disable();
this.digitalGaugeWidgetSettingsForm.get('showMinMax').enable({emitEvent: false});
if (showMinMax) {
this.digitalGaugeWidgetSettingsForm.get('minValue').enable();
this.digitalGaugeWidgetSettingsForm.get('maxValue').enable();
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').enable();
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').enable();
} else {
this.digitalGaugeWidgetSettingsForm.get('minValue').disable();
this.digitalGaugeWidgetSettingsForm.get('maxValue').disable();
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable();
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable();
}
}
if (showTitle) {
this.digitalGaugeWidgetSettingsForm.get('title').enable();
this.digitalGaugeWidgetSettingsForm.get('titleFont').enable();
this.digitalGaugeWidgetSettingsForm.get('titleColor').enable();
} else {
this.digitalGaugeWidgetSettingsForm.get('title').disable();
this.digitalGaugeWidgetSettingsForm.get('titleFont').disable();
this.digitalGaugeWidgetSettingsForm.get('titleColor').disable();
}
if (showUnitTitle) {
this.digitalGaugeWidgetSettingsForm.get('unitTitle').enable();
@ -201,25 +291,17 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
}
if (showUnitTitle || showTimestamp) {
this.digitalGaugeWidgetSettingsForm.get('labelFont').enable();
this.digitalGaugeWidgetSettingsForm.get('labelColor').enable();
} else {
this.digitalGaugeWidgetSettingsForm.get('labelFont').disable();
this.digitalGaugeWidgetSettingsForm.get('labelColor').disable();
}
if (showValue) {
this.digitalGaugeWidgetSettingsForm.get('valueFont').enable();
this.digitalGaugeWidgetSettingsForm.get('valueColor').enable();
} else {
this.digitalGaugeWidgetSettingsForm.get('valueFont').disable();
}
if (showMinMax) {
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').enable();
} else {
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable();
}
if (useFixedLevelColor) {
this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors').enable();
this.digitalGaugeWidgetSettingsForm.get('levelColors').disable();
} else {
this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors').disable();
this.digitalGaugeWidgetSettingsForm.get('levelColors').enable();
this.digitalGaugeWidgetSettingsForm.get('valueColor').disable();
}
if (showTicks) {
this.digitalGaugeWidgetSettingsForm.get('tickWidth').enable();
@ -240,13 +322,21 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('title').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('titleFont').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('titleColor').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('unitTitle').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('timestampFormat').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('labelFont').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('labelColor').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('valueFont').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('valueColor').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('minValue').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('maxValue').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('levelColors').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('tickWidth').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('colorTicks').updateValueAndValidity({emitEvent});
this.digitalGaugeWidgetSettingsForm.get('ticksValue').updateValueAndValidity({emitEvent});
@ -255,32 +345,10 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
}
protected doUpdateSettings(settingsForm: UntypedFormGroup, settings: WidgetSettings) {
settingsForm.setControl('levelColors', this.prepareLevelColorFormArray(settings.levelColors), {emitEvent: false});
settingsForm.setControl('fixedLevelColors', this.prepareFixedLevelColorFormArray(settings.fixedLevelColors), {emitEvent: false});
settingsForm.setControl('ticksValue', this.prepareTicksValueFormArray(settings.ticksValue), {emitEvent: false});
}
private prepareLevelColorFormArray(levelColors: string[] | undefined): UntypedFormArray {
const levelColorsControls: Array<AbstractControl> = [];
if (levelColors) {
levelColors.forEach((levelColor) => {
levelColorsControls.push(this.fb.control(levelColor, [Validators.required]));
});
}
return this.fb.array(levelColorsControls);
}
private prepareFixedLevelColorFormArray(fixedLevelColors: FixedColorLevel[] | undefined): UntypedFormArray {
const fixedLevelColorsControls: Array<AbstractControl> = [];
if (fixedLevelColors) {
fixedLevelColors.forEach((fixedLevelColor) => {
fixedLevelColorsControls.push(this.fb.control(fixedLevelColor, [fixedColorLevelValidator]));
});
}
return this.fb.array(fixedLevelColorsControls);
}
private prepareTicksValueFormArray(ticksValue: ValueSourceProperty[] | undefined): UntypedFormArray {
private prepareTicksValueFormArray(ticksValue: ValueSourceWithDataKey[] | undefined): UntypedFormArray {
const ticksValueControls: Array<AbstractControl> = [];
if (ticksValue) {
ticksValue.forEach((tickValue) => {
@ -290,71 +358,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
return this.fb.array(ticksValueControls);
}
levelColorsFormArray(): UntypedFormArray {
return this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray;
}
public trackByLevelColor(index: number, levelColorControl: AbstractControl): any {
return levelColorControl;
}
public removeLevelColor(index: number) {
(this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray).removeAt(index);
}
public addLevelColor() {
const levelColorsArray = this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray;
const levelColorControl = this.fb.control(null, []);
levelColorsArray.push(levelColorControl);
this.digitalGaugeWidgetSettingsForm.updateValueAndValidity();
}
levelColorDrop(event: CdkDragDrop<string[]>) {
const levelColorsArray = this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray;
const levelColor = levelColorsArray.at(event.previousIndex);
levelColorsArray.removeAt(event.previousIndex);
levelColorsArray.insert(event.currentIndex, levelColor);
}
fixedLevelColorFormArray(): UntypedFormArray {
return this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray;
}
public trackByFixedLevelColor(index: number, fixedLevelColorControl: AbstractControl): any {
return fixedLevelColorControl;
}
public removeFixedLevelColor(index: number) {
(this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray).removeAt(index);
}
public addFixedLevelColor() {
const fixedLevelColor: FixedColorLevel = {
from: {
valueSource: 'predefinedValue'
},
to: {
valueSource: 'predefinedValue'
},
color: null
};
const fixedLevelColorsArray = this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray;
const fixedLevelColorControl = this.fb.control(fixedLevelColor, [fixedColorLevelValidator]);
(fixedLevelColorControl as any).new = true;
fixedLevelColorsArray.push(fixedLevelColorControl);
this.digitalGaugeWidgetSettingsForm.updateValueAndValidity();
if (!this.digitalGaugeWidgetSettingsForm.valid) {
this.onSettingsChanged(this.digitalGaugeWidgetSettingsForm.value);
}
}
fixedLevelColorDrop(event: CdkDragDrop<string[]>) {
const fixedLevelColorsArray = this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray;
const fixedLevelColor = fixedLevelColorsArray.at(event.previousIndex);
fixedLevelColorsArray.removeAt(event.previousIndex);
fixedLevelColorsArray.insert(event.currentIndex, fixedLevelColor);
}
tickValuesFormArray(): UntypedFormArray {
return this.digitalGaugeWidgetSettingsForm.get('ticksValue') as UntypedFormArray;
}
@ -368,8 +371,8 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
}
public addTickValue() {
const tickValue: ValueSourceProperty = {
valueSource: 'predefinedValue'
const tickValue: ValueSourceWithDataKey = {
type: ValueSourceDataKeyType.constant
};
const tickValuesArray = this.digitalGaugeWidgetSettingsForm.get('ticksValue') as UntypedFormArray;
const tickValueControl = this.fb.control(tickValue, []);
@ -385,4 +388,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
tickValuesArray.insert(event.currentIndex, tickValue);
}
private _valuePreviewFn(units: boolean): string {
return formatValue(22, 0, units ? this.widget.config.units : null, true);
}
}

60
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.html

@ -1,60 +0,0 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-expansion-panel class="fixed-color-level" fxFlex [formGroup]="fixedColorLevelFormGroup" [(expanded)]="expanded">
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
<div>{{ fixedColorLevelRangeText() }}</div>
<div class="tb-color-preview" style="margin-left: 6px;">
<div class="tb-color-result" [ngStyle]="{background: fixedColorLevelFormGroup.get('color').value}"></div>
</div>
</div>
</mat-panel-title>
<span fxFlex></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeFixedColorLevel.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxLayout="column" fxLayoutGap="0.5em">
<mat-divider></mat-divider>
<section class="tb-widget-settings" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.from</legend>
<tb-value-source formControlName="from" [aliasController]="aliasController"></tb-value-source>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gauge.to</legend>
<tb-value-source formControlName="to" [aliasController]="aliasController"></tb-value-source>
</fieldset>
<tb-color-input
required
formControlName="color"
icon="format_color_fill"
label="{{ 'widgets.gauge.color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</section>
</div>
</ng-template>
</mat-expansion-panel>

147
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts

@ -1,147 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component';
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
UntypedFormBuilder,
UntypedFormGroup,
NG_VALUE_ACCESSOR, ValidationErrors,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { isNumber } from '@core/utils';
import { IAliasController } from '@core/api/widget-api.models';
export interface FixedColorLevel {
from?: ValueSourceProperty;
to?: ValueSourceProperty;
color: string;
}
export function fixedColorLevelValidator(control: AbstractControl): ValidationErrors | null {
const fixedColorLevel: FixedColorLevel = control.value;
if (!fixedColorLevel || !fixedColorLevel.color) {
return {
fixedColorLevel: true
};
}
return null;
}
@Component({
selector: 'tb-fixed-color-level',
templateUrl: './fixed-color-level.component.html',
styleUrls: ['./fixed-color-level.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FixedColorLevelComponent),
multi: true
}
]
})
export class FixedColorLevelComponent extends PageComponent implements OnInit, ControlValueAccessor {
@Input()
disabled: boolean;
@Input()
expanded = false;
@Input()
aliasController: IAliasController;
@Output()
removeFixedColorLevel = new EventEmitter();
private modelValue: FixedColorLevel;
private propagateChange = null;
public fixedColorLevelFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private fb: UntypedFormBuilder) {
super(store);
}
ngOnInit(): void {
this.fixedColorLevelFormGroup = this.fb.group({
from: [null, []],
to: [null, []],
color: [null, [Validators.required]]
});
this.fixedColorLevelFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.fixedColorLevelFormGroup.disable({emitEvent: false});
} else {
this.fixedColorLevelFormGroup.enable({emitEvent: false});
}
}
writeValue(value: FixedColorLevel): void {
this.modelValue = value;
this.fixedColorLevelFormGroup.patchValue(
value, {emitEvent: false}
);
}
fixedColorLevelRangeText(): string {
const value: FixedColorLevel = this.fixedColorLevelFormGroup.value;
const from = this.valueSourcePropertyText(value?.from);
const to = this.valueSourcePropertyText(value?.to);
return `${from} - ${to}`;
}
private valueSourcePropertyText(source?: ValueSourceProperty): string {
if (source) {
if (source.valueSource === 'predefinedValue') {
return `${isNumber(source.value) ? source.value : 0}`;
} else if (source.valueSource === 'entityAttribute') {
const alias = source.entityAlias || 'Undefined';
const key = source.attribute || 'Undefined';
return `${alias}.${key}`;
}
}
return 'Undefined';
}
private updateModel() {
const value: FixedColorLevel = this.fixedColorLevelFormGroup.value;
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}

44
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html

@ -15,30 +15,20 @@
limitations under the License.
-->
<mat-expansion-panel class="tick-value" fxFlex [formGroup]="tickValueFormGroup" [(expanded)]="expanded">
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
<div>{{ tickValueText() }}</div>
</div>
</mat-panel-title>
<span fxFlex></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeTickValue.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxLayout="column" fxLayoutGap="0.5em">
<mat-divider></mat-divider>
<section class="tb-widget-settings" fxLayout="column">
<tb-value-source formControlName="tickValue" [aliasController]="aliasController"></tb-value-source>
</section>
</div>
</ng-template>
</mat-expansion-panel>
<div fxLayout="row" [formGroup]="tickValueFormGroup">
<tb-value-source-data-key fxFlex
formControlName="tickValue"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<button *ngIf="!disabled" mat-icon-button
class="tb-box-button"
type="button"
(click)="removeTickValue.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>

32
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts

@ -16,18 +16,19 @@
import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component';
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { isNumber } from '@core/utils';
import { IAliasController } from '@core/api/widget-api.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({
selector: 'tb-tick-value',
templateUrl: './tick-value.component.html',
styleUrls: ['./tick-value.component.scss'],
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -42,10 +43,13 @@ export class TickValueComponent extends PageComponent implements OnInit, Control
disabled: boolean;
@Input()
expanded = false;
aliasController: IAliasController;
@Input()
aliasController: IAliasController;
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Output()
removeTickValue = new EventEmitter();
@ -94,24 +98,6 @@ export class TickValueComponent extends PageComponent implements OnInit, Control
);
}
tickValueText(): string {
const value: ValueSourceProperty = this.tickValueFormGroup.get('tickValue').value;
return this.valueSourcePropertyText(value);
}
private valueSourcePropertyText(source?: ValueSourceProperty): string {
if (source) {
if (source.valueSource === 'predefinedValue') {
return `${isNumber(source.value) ? source.value : 0}`;
} else if (source.valueSource === 'entityAttribute') {
const alias = source.entityAlias || 'Undefined';
const key = source.attribute || 'Undefined';
return `${alias}.${key}`;
}
}
return 'Undefined';
}
private updateModel() {
const value: ValueSourceProperty = this.tickValueFormGroup.get('tickValue').value;
this.modelValue = value;

28
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html

@ -48,18 +48,40 @@
[autoScale]="batteryLevelWidgetSettingsForm.get('autoScaleValueSize').value"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="valueColor" settingsKey="{{'widgets.battery-level.value' | translate }}">
<tb-color-settings formControlName="valueColor"
rangeAdvancedMode
gradientAdvancedMode
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.battery-level.battery-level-color' | translate }}</div>
<tb-color-settings formControlName="batteryLevelColor" settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
<tb-color-settings formControlName="batteryLevelColor"
rangeAdvancedMode
gradientAdvancedMode
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.battery-level.battery-shape-color' | translate }}</div>
<tb-color-settings formControlName="batteryShapeColor" settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
<tb-color-settings formControlName="batteryShapeColor"
rangeAdvancedMode
gradientAdvancedMode
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">

11
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts

@ -15,7 +15,7 @@
///
import { Component, Injector } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -48,6 +48,15 @@ export class BatteryLevelWidgetSettingsComponent extends WidgetSettingsComponent
return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout);
}
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
constructor(protected store: Store<AppState>,
private $injector: Injector,
private fb: UntypedFormBuilder) {

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -74,7 +74,6 @@ import {
import {
DigitalGaugeWidgetSettingsComponent
} from '@home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component';
import { FixedColorLevelComponent } from '@home/components/widget/lib/settings/gauge/fixed-color-level.component';
import { TickValueComponent } from '@home/components/widget/lib/settings/gauge/tick-value.component';
import { FlotWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/flot-widget-settings.component';
import {
@ -380,7 +379,6 @@ import {
AnalogueLinearGaugeWidgetSettingsComponent,
AnalogueCompassWidgetSettingsComponent,
DigitalGaugeWidgetSettingsComponent,
FixedColorLevelComponent,
TickValueComponent,
FlotWidgetSettingsComponent,
LabelDataKeyComponent,
@ -513,7 +511,6 @@ import {
AnalogueLinearGaugeWidgetSettingsComponent,
AnalogueCompassWidgetSettingsComponent,
DigitalGaugeWidgetSettingsComponent,
FixedColorLevelComponent,
TickValueComponent,
FlotWidgetSettingsComponent,
LabelDataKeyComponent,

1
ui-ngx/src/app/shared/components/color-input.component.html

@ -38,6 +38,7 @@
<button type="button"
mat-stroked-button
class="tb-box-button"
[ngClass]="{'button-no-border': noBorder}"
[disabled]="disabled"
#matButton
(click)="openColorPickerPopup($event, matButton._elementRef)">

4
ui-ngx/src/app/shared/components/color-input.component.scss

@ -31,4 +31,8 @@
margin: 0;
}
}
.button-no-border {
border: none;
}
}

4
ui-ngx/src/app/shared/components/color-input.component.ts

@ -77,6 +77,10 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro
@coerceBoolean()
openOnInput = false;
@Input()
@coerceBoolean()
noBorder = false;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;

449
ui-ngx/src/app/shared/models/widget-settings.models.ts

@ -22,20 +22,25 @@ import {
DatasourceData,
DatasourceType,
TargetDevice,
TargetDeviceType
TargetDeviceType,
widgetType
} from '@shared/models/widget.models';
import { Injector } from '@angular/core';
import { ChangeDetectorRef, EventEmitter, Injector } from '@angular/core';
import { DatePipe } from '@angular/common';
import { DateAgoPipe } from '@shared/pipe/date-ago.pipe';
import { TranslateService } from '@ngx-translate/core';
import { AlarmFilterConfig } from '@shared/models/query/query.models';
import { AlarmSearchStatus } from '@shared/models/alarm.models';
import { Observable, of } from 'rxjs';
import { EMPTY, Observable, of } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { map } from 'rxjs/operators';
import { DomSanitizer } from '@angular/platform-browser';
import { AVG_MONTH, DAY, HOUR, Interval, IntervalMath, MINUTE, SECOND, YEAR } from '@shared/models/time/time.models';
import moment from 'moment';
import tinycolor, { ColorInput } from 'tinycolor2';
import { WidgetContext } from '@home/models/widget-component.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
export type ComponentStyle = {[klass: string]: any};
@ -81,6 +86,7 @@ export interface Font {
export enum ColorType {
constant = 'constant',
gradient = 'gradient',
range = 'range',
function = 'function'
}
@ -88,6 +94,7 @@ export enum ColorType {
export const colorTypeTranslations = new Map<ColorType, string>(
[
[ColorType.constant, 'widgets.color.color-type-constant'],
[ColorType.gradient, 'widgets.color.color-type-gradient'],
[ColorType.range, 'widgets.color.color-type-range'],
[ColorType.function, 'widgets.color.color-type-function']
]
@ -99,6 +106,38 @@ export interface ColorRange {
color: string;
}
export enum ValueSourceDataKeyType {
constant = 'constant',
latestKey = 'latestKey',
entity = 'entity'
}
export const ValueSourceDataKeyTypes = Object.keys(ValueSourceDataKeyType) as ValueSourceDataKeyType[];
export const ValueSourceDataKeyTypeTranslation = new Map<ValueSourceDataKeyType, string>(
[
[ValueSourceDataKeyType.constant, 'widgets.value-source.type-constant'],
[ValueSourceDataKeyType.latestKey, 'widgets.value-source.type-latest-key'],
[ValueSourceDataKeyType.entity, 'widgets.value-source.type-entity']
]
);
export interface ValueSourceWithDataKey {
type: ValueSourceDataKeyType;
value?: number;
latestKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
latestKey?: string;
entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
entityAlias?: string;
entityKey?: string;
}
export interface AdvancedColorRange {
from?: ValueSourceWithDataKey;
to?: ValueSourceWithDataKey;
color: string;
}
export const colorRangeIncludes = (range: ColorRange, toCheck: ColorRange): boolean => {
if (isNumber(range.from) && isNumber(range.to)) {
if (isNumber(toCheck.from) && isNumber(toCheck.to)) {
@ -175,10 +214,35 @@ export const sortedColorRange = (ranges: Array<ColorRange>): Array<ColorRange> =
export interface ColorSettings {
type: ColorType;
color: string;
rangeList?: ColorRange[];
rangeList?: ColorRangeSettings;
gradient?: ColorGradientSettings;
colorFunction?: string;
}
export interface ColorRangeSettings {
advancedMode?: boolean;
range?: ColorRange[];
rangeAdvanced?: AdvancedColorRange[];
}
export interface AdvancedGradient {
source: ValueSourceWithDataKey;
color: string;
}
export interface ColorGradientSettings {
advancedMode?: boolean;
gradient?: Array<string>;
gradientAdvanced?: Array<AdvancedGradient>;
minValue?: number;
maxValue?: number;
}
export interface DataKeySettings {
color?: string;
index: number;
}
export interface TimewindowStyle {
showIcon: boolean;
icon: string;
@ -203,6 +267,33 @@ export const constantColor = (color: string): ColorSettings => ({
colorFunction: defaultColorFunction
});
export const defaultGradient = (minValue?: number, maxValue?: number): ColorGradientSettings => ({
advancedMode: false,
gradient: ['rgba(0, 255, 0, 1)', 'rgba(255, 0, 0, 1)'],
gradientAdvanced: [
{
source: {type: ValueSourceDataKeyType.constant},
color: 'rgba(0, 255, 0, 1)'
},
{
source: {type: ValueSourceDataKeyType.constant},
color: 'rgba(255, 0, 0, 1)'
}
],
minValue: isDefinedAndNotNull(minValue) ? minValue : 0,
maxValue: isDefinedAndNotNull(maxValue) ? maxValue : 100
});
export const updateGradientMinMaxValues = (colorSettings: ColorSettings, minValue: number, maxValue: number): ColorSettings => {
if (isDefinedAndNotNull(colorSettings.gradient)) {
colorSettings.gradient.minValue = minValue;
colorSettings.gradient.maxValue = maxValue;
} else {
colorSettings.gradient = defaultGradient(minValue, maxValue);
}
return colorSettings;
};
export const defaultColorFunction = 'var temperature = value;\n' +
'if (typeof temperature !== undefined) {\n' +
' var percent = (temperature + 60)/120 * 100;\n' +
@ -248,15 +339,17 @@ type ValueColorFunction = (value: any) => string;
export abstract class ColorProcessor {
static fromSettings(color: ColorSettings): ColorProcessor {
static fromSettings(color: ColorSettings, ctx?: WidgetContext): ColorProcessor {
const settings = color || constantColor(null);
switch (settings.type) {
case ColorType.constant:
return new ConstantColorProcessor(settings);
case ColorType.range:
return new RangeColorProcessor(settings);
return new RangeColorProcessor(settings, ctx);
case ColorType.function:
return new FunctionColorProcessor(settings);
case ColorType.gradient:
return new GradientColorProcessor(settings, ctx);
default:
return new ConstantColorProcessor(settings);
}
@ -264,11 +357,81 @@ export abstract class ColorProcessor {
color: string;
colorUpdated: EventEmitter<void> = new EventEmitter<void>();
protected constructor(protected settings: ColorSettings) {
this.color = settings.color;
}
abstract update(value: any): void;
abstract destroy(): void;
static generateDatasource(
ctx: WidgetContext,
datasources: Datasource[],
valueSource: ValueSourceWithDataKey,
settings: DataKeySettings): Datasource[]{
if (valueSource.type === ValueSourceDataKeyType.latestKey) {
if (ctx.datasources.length) {
if (isDefinedAndNotNull(ctx.datasources[0].aliasName)) {
datasources = this.parseDatasource(ctx, valueSource, ctx.datasources[0].aliasName, settings, datasources, true);
} else {
datasources = this.parseDatasource(ctx, valueSource, '', settings, datasources, true);
}
}
} else if (valueSource.type === ValueSourceDataKeyType.entity) {
datasources = this.parseDatasource(ctx, valueSource, valueSource.entityAlias, settings, datasources, false);
}
return datasources;
}
static parseDatasource(ctx: WidgetContext,
valueSource: ValueSourceWithDataKey,
entityAlias: string, settings: DataKeySettings,
datasources: Datasource[],
isLatest: boolean) {
let dataKey: DataKey = null;
const dataKeySettings = [settings];
const entityAliasId = ctx.aliasController.getEntityAliasId(entityAlias);
let datasource = datasources.find(d =>
entityAlias ? (d.entityAliasId === entityAliasId) : (d.deviceId === ctx.datasources[0].deviceId)
);
dataKey = {
type: isLatest ? valueSource.latestKeyType : valueSource.entityKeyType,
name: isLatest ? valueSource.latestKey : valueSource.entityKey,
label: isLatest ? valueSource.latestKey : valueSource.entityKey,
settings: {dataKeySettings}
};
if (datasource) {
const findDataKey = datasource.dataKeys.find(dataKeyIteration =>
dataKeyIteration.name === (isLatest ? valueSource.latestKey : valueSource.entityKey));
if (findDataKey) {
findDataKey.settings.dataKeySettings.push(settings);
} else {
datasource.dataKeys.push(dataKey);
}
} else {
if (entityAlias) {
datasource = {
type: DatasourceType.entity,
name: entityAlias,
aliasName: entityAlias,
entityAliasId,
dataKeys: [ dataKey ]
};
} else {
datasource = {
type: DatasourceType.device,
name: ctx.datasources[0].name,
deviceName: ctx.datasources[0].name,
deviceId: ctx.datasources[0].deviceId,
dataKeys: [ dataKey ]
};
}
datasources.push(datasource);
}
return datasources;
}
}
@ -278,25 +441,134 @@ class ConstantColorProcessor extends ColorProcessor {
}
update(value: any): void {}
destroy(): void {}
}
class RangeColorProcessor extends ColorProcessor {
constructor(protected settings: ColorSettings) {
private advancedRangeSourcesSubscription: IWidgetSubscription;
private progress;
constructor(protected settings: ColorSettings,
protected ctx: WidgetContext) {
super(settings);
}
advancedRangeSubscribe(options: ColorRangeSettings){
let advancedRangeDatasource: Datasource[] = [];
let index = 0;
function setRangeColor(rangeSetting: ValueSourceWithDataKey, color: string) {
if (rangeSetting.type === ValueSourceDataKeyType.constant && isFinite(rangeSetting.value)) {
index++;
} else {
try {
advancedRangeDatasource = this.generateDatasource(this.ctx, advancedRangeDatasource,
rangeSetting, {color, index});
} catch (e) {
return;
}
index++;
}
}
for (const range of options.rangeAdvanced) {
if (range.from) {
setRangeColor.call(this, range.from, range.color);
}
if (range.to) {
setRangeColor.call(this, range.to, range.color);
}
}
if (advancedRangeDatasource.length) {
this.subscribeAttributes(advancedRangeDatasource).subscribe((subscription) => {
this.advancedRangeSourcesSubscription = subscription;
});
} else {
this.color = this.computeFromRange(this.progress);
this.colorUpdated.emit();
}
}
subscribeAttributes(datasource: Datasource[]): Observable<IWidgetSubscription> {
if (!datasource.length) {
return EMPTY;
}
const levelColorsSourcesSubscriptionOptions: WidgetSubscriptionOptions = {
datasources: datasource,
useDashboardTimewindow: false,
type: widgetType.latest,
callbacks: {
onDataUpdated: (subscription) => {
this.updateAttribute(subscription.data);
this.color = this.computeFromRange(this.progress);
this.colorUpdated.emit();
}
}
};
return this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true);
}
updateAttribute(data: Array<DatasourceData>) {
for (const keyData of data) {
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) {
for (const setting of keyData.dataKey.settings.dataKeySettings) {
const index = Math.floor(setting.index/2);
if (index === setting.index/2) {
this.settings.rangeList.rangeAdvanced[index].from.value = attrValue;
} else {
this.settings.rangeList.rangeAdvanced[index].to.value = attrValue;
}
}
}
}
}
}
update(value: any): void {
this.color = this.computeFromRange(value);
this.progress = value;
if(isDefinedAndNotNull(this.settings.rangeList?.advancedMode) && this.settings.rangeList?.advancedMode) {
this.advancedRangeSubscribe(this.settings.rangeList);
} else {
this.color = this.computeFromRange(value);
}
}
destroy(): void {
if (this.advancedRangeSourcesSubscription) {
this.ctx.subscriptionApi.removeSubscription(this.advancedRangeSourcesSubscription.id);
}
}
private computeFromRange(value: any): string {
if (this.settings.rangeList?.length && isDefinedAndNotNull(value) && isNumeric(value)) {
let rangeList: any = this.settings.rangeList as Array<ColorRange>;
let advancedMode = false;
if (isDefinedAndNotNull(this.settings.rangeList?.advancedMode)) {
advancedMode = this.settings.rangeList.advancedMode;
rangeList = advancedMode ?
this.settings.rangeList.rangeAdvanced as Array<AdvancedColorRange> :
this.settings.rangeList.range as Array<ColorRange>;
}
if (rangeList.length && isDefinedAndNotNull(value) && isNumeric(value)) {
const num = Number(value);
for (const range of this.settings.rangeList) {
if (RangeColorProcessor.constantRange(range) && range.from === num) {
for (const range of rangeList) {
if (advancedMode ?
(RangeColorProcessor.constantAdvancedRange(range) && range.from.value === num ) :
(RangeColorProcessor.constantRange(range) && range.from === num)
) {
return range.color;
} else if ((!isNumber(range.from) || num >= range.from) && (!isNumber(range.to) || num < range.to)) {
} else if (advancedMode ?
((!isNumber(range.from.value) || num >= range.from.value) && (!isNumber(range.to.value) || num < range.to.value)) :
((!isNumber(range.from) || num >= range.from) && (!isNumber(range.to) || num < range.to))
) {
return range.color;
}
}
@ -307,6 +579,9 @@ class RangeColorProcessor extends ColorProcessor {
public static constantRange(range: ColorRange): boolean {
return isNumber(range.from) && isNumber(range.to) && range.from === range.to;
}
public static constantAdvancedRange(range: AdvancedColorRange): boolean {
return isNumber(range.from.value) && isNumber(range.to.value) && range.from.value === range.to.value;
}
}
class FunctionColorProcessor extends ColorProcessor {
@ -323,6 +598,156 @@ class FunctionColorProcessor extends ColorProcessor {
this.color = this.colorFunction(value) || this.settings.color;
}
}
destroy(): void {}
}
class GradientColorProcessor extends ColorProcessor {
private progress: number;
private minValue: number;
private maxValue: number;
private advancedGradientSourcesSubscription: IWidgetSubscription;
constructor(protected settings: ColorSettings,
protected ctx: WidgetContext) {
super(settings);
this.minValue = isDefinedAndNotNull(settings.gradient.minValue) ? settings.gradient.minValue : 0;
this.maxValue = isDefinedAndNotNull(settings.gradient.maxValue) ? settings.gradient.maxValue : 100;
}
update(value: any): void {
this.progress = this.calculateProgress(+value, this.minValue, this.maxValue);
if(isDefinedAndNotNull(this.settings.gradient.advancedMode) && this.settings.gradient.advancedMode) {
this.advancedRangeSubscribe(this.settings.gradient.gradientAdvanced);
} else {
this.color = this.getProgressColor(this.progress, this.settings.gradient.gradient);
}
}
destroy(): void {
if (this.advancedGradientSourcesSubscription) {
this.ctx.subscriptionApi.removeSubscription(this.advancedGradientSourcesSubscription.id);
}
}
calculateProgress(current: number, min: number, max: number) {
if (current < min) {
return 0;
} else if (current > max) {
return 1;
}
return (current - min) / (max - min);
}
getProgressColor(progress: number, levelColors: Array<AdvancedGradient | string>): string {
const colorsCount = levelColors.length;
const inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
const colorsRange = [];
levelColors.forEach((levelColor, i) => {
const color = typeof levelColor === 'string' ? levelColor : levelColor.color;
if (color !== null) {
const tColor = tinycolor(color);
colorsRange.push({
pct: typeof levelColor !== 'string' ?
this.calculateProgress(+levelColor.source.value, this.minValue, this.maxValue) : inc * i,
color: tColor.toRgb(),
alpha: tColor.getAlpha(),
rgbString: tColor.toRgbString()
});
}
});
if (progress === 0 || colorsRange.length === 1) {
return colorsRange[0].rgbString;
}
for (let j = 1; j < colorsRange.length; j++) {
if (progress <= colorsRange[j].pct) {
const lower = colorsRange[j - 1];
const upper = colorsRange[j];
const range = upper.pct - lower.pct;
const rangePct = (progress - lower.pct) / range;
const pctLower = 1 - rangePct;
const pctUpper = rangePct;
const color = tinycolor({
r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper),
a: (lower.alpha + upper.alpha)/2
});
return color.toRgbString();
}
}
return colorsRange[colorsRange.length - 1].rgbString;
}
advancedRangeSubscribe(options: Array<AdvancedGradient>){
let advancedGradientDatasource: Datasource[] = [];
let index = 0;
function setGradientColor(gradientSetting: ValueSourceWithDataKey) {
if (gradientSetting.type === ValueSourceDataKeyType.constant && isFinite(gradientSetting.value)) {
index++;
} else {
try {
advancedGradientDatasource = this.generateDatasource(this.ctx, advancedGradientDatasource,
gradientSetting, {index});
} catch (e) {
return;
}
index++;
}
}
for (const gradient of options) {
setGradientColor.call(this, gradient.source);
}
if (advancedGradientDatasource.length) {
this.subscribeAttributes(advancedGradientDatasource).subscribe((subscription) => {
this.advancedGradientSourcesSubscription = subscription;
});
} else {
this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced);
this.colorUpdated.emit();
}
}
subscribeAttributes(datasource: Datasource[]): Observable<IWidgetSubscription> {
if (!datasource.length) {
return EMPTY;
}
const levelColorsSourcesSubscriptionOptions: WidgetSubscriptionOptions = {
datasources: datasource,
useDashboardTimewindow: false,
type: widgetType.latest,
callbacks: {
onDataUpdated: (subscription) => {
this.updateAttribute(subscription.data);
this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced);
this.colorUpdated.emit();
}
}
};
return this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true);
}
updateAttribute(data: Array<DatasourceData>) {
for (const keyData of data) {
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) {
for (const setting of keyData.dataKey.settings.dataKeySettings) {
this.settings.gradient.gradientAdvanced[setting.index].source.value = attrValue;
}
}
}
}
}
}
export type FormatTimeUnit = 'millisecond' | 'second' | 'minute' | 'hour'

53
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -5896,6 +5896,7 @@
"color": {
"color-settings": "Color settings",
"color-type-constant": "Constant",
"color-type-gradient": "Gradient",
"color-type-range": "Range",
"color-type-function": "Function",
"color": "Color",
@ -5903,7 +5904,22 @@
"from": "From",
"to": "To",
"color-function": "Color function",
"copy-color-settings-from": "Copy color settings from"
"copy-color-settings-from": "Copy color settings from",
"copy-from": "Copy from",
"settings-type": "Settings type",
"basic-mode": "Basic",
"advanced-mode": "Advanced",
"entity-alias": "Entity alias",
"entity-attribute": "Entity attribute",
"gradient-color": "Gradient color",
"gradient-color-min": "Color",
"gradient-start": "Gradient start color",
"gradient-start-min": "Start",
"gradient-end": "Gradient end color",
"gradient-end-min": "End",
"start-value": "Start value",
"end-value": "End value",
"gradient-type": "Gradient type"
},
"dashboard-state": {
"dashboard-state-settings": "Dashboard state settings",
@ -6052,7 +6068,7 @@
"minor-ticks-color": "Minor ticks color",
"tick-numbers-font": "Tick numbers font",
"unit-title-settings": "Unit title settings",
"show-unit-title": "Show unit title",
"show-unit-title": "Units title",
"unit-title": "Unit title",
"title-font": "Title text font",
"units-settings": "Units settings",
@ -6132,12 +6148,14 @@
"gauge-type-donut": "Donut",
"gauge-type-horizontal-bar": "Horizontal bar",
"gauge-type-vertical-bar": "Vertical bar",
"donut-start-angle": "Angle to start from",
"donut-start-angle": "Angle to start from (degrees)",
"bar-settings": "Gauge bar settings",
"relative-bar-width": "Relative bar width",
"neon-glow-brightness": "Neon glow effect brightness, (0-100), 0 - disable effect",
"stripes-thickness": "Thickness of the stripes, 0 - no stripes",
"rounded-line-cap": "Display rounded line cap",
"neon-glow-brightness": "Neon glow effect brightness (0-100)",
"neon-glow-brightness-hint": "0 - disable effect ",
"stripes-thickness": "Thickness of the stripes",
"stripes-thickness-hint": "0 - no stripes",
"rounded-line-cap": "Rounded line cap",
"bar-color-settings": "Bar color settings",
"use-precise-level-color-values": "Use precise color levels",
"bar-colors": "Bar colors, from lower to upper",
@ -6151,8 +6169,8 @@
"show-gauge-title": "Show gauge title",
"gauge-title": "Gauge title",
"gauge-title-font": "Gauge title font",
"unit-title-and-timestamp-settings": "Unit title and timestamp settings",
"show-timestamp": "Show value timestamp",
"unit-title-and-timestamp-settings": "Units title and timestamp settings",
"show-timestamp": "Timestamp",
"timestamp-format": "Timestamp format",
"label-font": "Font of label showing under value",
"value-settings": "Value settings",
@ -6175,8 +6193,15 @@
"scale": "Scale",
"scale-color": "Scale colors",
"compass-appearance": "Compass appearance",
"label": "Label",
"labels": "Labels",
"label-style": "Label style"
"label-style": "Label style",
"simple-gauge-type": "Type",
"gauge-bar-background": "Gauge bar background",
"bar-color": "Bar color",
"min-and-max-value": "Min and max value",
"font": "Font",
"tick-width-and-color": "Tick width and color"
},
"gpio": {
"pin": "Pin",
@ -7209,11 +7234,17 @@
},
"value-source": {
"value-source": "Value source",
"predefined-value": "Predefined value",
"predefined-value": "Constant",
"entity-attribute": "Entity attribute",
"value": "Value",
"value-required": "Value is required.",
"key-required": "Key is required.",
"entity-key-required": "Entity key is required.",
"source-entity-alias": "Source entity alias",
"source-entity-attribute": "Source entity attribute"
"source-entity-attribute": "Source entity attribute",
"type-constant": "Constant",
"type-latest-key": "Key",
"type-entity": "Entity"
},
"rpc-state": {
"initial-state": "Initial state",

31
ui-ngx/src/assets/widget/simple-gauge/arc-layout.svg

@ -0,0 +1,31 @@
<svg width="213" height="213" viewBox="0 0 213 213" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_3154_417028)">
<rect x="8" y="4" width="197" height="197" rx="4" fill="white" shape-rendering="crispEdges"/>
<g filter="url(#filter1_b_3154_417028)">
<rect x="20" y="16" width="173" height="173" rx="4" fill="white" fill-opacity="0.76"/>
</g>
<path d="M170 124.5C170 107.659 163.31 91.5073 151.401 79.5987C139.493 67.6902 123.341 61 106.5 61C89.6588 61 73.5073 67.6902 61.5987 79.5987C49.6902 91.5073 43 107.659 43 124.5L63.0025 124.5C63.0025 112.964 67.5853 101.9 75.7426 93.7426C83.9 85.5853 94.9637 81.0025 106.5 81.0025C118.036 81.0025 129.1 85.5853 137.257 93.7426C145.415 101.9 149.997 112.964 149.997 124.5H170Z" fill="black" fill-opacity="0.06"/>
<path d="M170 124.5C170 116.161 168.358 107.904 165.166 100.2C161.975 92.4954 157.298 85.4952 151.401 79.5987L137.257 93.7435C141.296 97.7825 144.499 102.577 146.685 107.855C148.871 113.132 149.996 118.788 149.996 124.5H170Z" fill="#2196F3"/>
<path d="M105.848 121.875V124.5H92.5039V122.244L98.9844 115.176C99.6953 114.374 100.256 113.681 100.666 113.098C101.076 112.514 101.363 111.99 101.527 111.525C101.701 111.051 101.787 110.591 101.787 110.145C101.787 109.516 101.669 108.964 101.432 108.49C101.204 108.007 100.867 107.629 100.42 107.355C99.9733 107.073 99.431 106.932 98.793 106.932C98.0547 106.932 97.4349 107.091 96.9336 107.41C96.4323 107.729 96.054 108.171 95.7988 108.736C95.5436 109.292 95.416 109.93 95.416 110.65H92.1211C92.1211 109.493 92.3854 108.436 92.9141 107.479C93.4427 106.512 94.2083 105.747 95.2109 105.182C96.2135 104.607 97.4212 104.32 98.834 104.32C100.165 104.32 101.295 104.544 102.225 104.99C103.154 105.437 103.861 106.07 104.344 106.891C104.836 107.711 105.082 108.682 105.082 109.803C105.082 110.423 104.982 111.038 104.781 111.648C104.581 112.259 104.294 112.87 103.92 113.48C103.555 114.082 103.122 114.688 102.621 115.299C102.12 115.9 101.568 116.511 100.967 117.131L96.6602 121.875H105.848ZM122.201 117.404V120.029H107.845L107.736 118.047L116.322 104.594H118.961L116.103 109.488L111.168 117.404H122.201ZM119.713 104.594V124.5H116.418V104.594H119.713Z" fill="black" fill-opacity="0.54"/>
<path d="M153.916 133.137V141H152.922V134.377L150.918 135.108V134.211L153.76 133.137H153.916ZM161.997 136.467V137.659C161.997 138.3 161.939 138.841 161.825 139.281C161.71 139.722 161.545 140.076 161.331 140.345C161.116 140.613 160.856 140.808 160.552 140.93C160.251 141.048 159.911 141.107 159.531 141.107C159.23 141.107 158.953 141.07 158.699 140.995C158.444 140.919 158.215 140.799 158.011 140.635C157.811 140.466 157.639 140.248 157.496 139.979C157.352 139.711 157.243 139.385 157.168 139.002C157.093 138.619 157.055 138.171 157.055 137.659V136.467C157.055 135.826 157.112 135.289 157.227 134.855C157.345 134.422 157.512 134.075 157.727 133.813C157.941 133.549 158.199 133.359 158.5 133.244C158.804 133.13 159.145 133.072 159.521 133.072C159.825 133.072 160.104 133.11 160.358 133.185C160.616 133.257 160.845 133.373 161.046 133.534C161.246 133.692 161.417 133.903 161.556 134.168C161.699 134.429 161.809 134.75 161.884 135.129C161.959 135.509 161.997 135.955 161.997 136.467ZM160.998 137.82V136.3C160.998 135.949 160.976 135.641 160.933 135.376C160.894 135.108 160.835 134.879 160.756 134.689C160.677 134.499 160.577 134.345 160.455 134.227C160.337 134.109 160.199 134.023 160.042 133.969C159.888 133.912 159.714 133.883 159.521 133.883C159.284 133.883 159.075 133.928 158.892 134.018C158.709 134.104 158.556 134.241 158.43 134.431C158.308 134.621 158.215 134.87 158.151 135.178C158.086 135.486 158.054 135.86 158.054 136.3V137.82C158.054 138.171 158.074 138.481 158.113 138.75C158.156 139.018 158.219 139.251 158.301 139.448C158.384 139.641 158.484 139.8 158.602 139.926C158.72 140.051 158.856 140.144 159.01 140.205C159.168 140.262 159.341 140.291 159.531 140.291C159.775 140.291 159.988 140.244 160.17 140.151C160.353 140.058 160.505 139.913 160.627 139.716C160.752 139.516 160.845 139.26 160.906 138.948C160.967 138.633 160.998 138.257 160.998 137.82ZM168.434 136.467V137.659C168.434 138.3 168.377 138.841 168.262 139.281C168.148 139.722 167.983 140.076 167.768 140.345C167.553 140.613 167.294 140.808 166.989 140.93C166.688 141.048 166.348 141.107 165.969 141.107C165.668 141.107 165.39 141.07 165.136 140.995C164.882 140.919 164.653 140.799 164.449 140.635C164.248 140.466 164.076 140.248 163.933 139.979C163.79 139.711 163.681 139.385 163.605 139.002C163.53 138.619 163.493 138.171 163.493 137.659V136.467C163.493 135.826 163.55 135.289 163.665 134.855C163.783 134.422 163.949 134.075 164.164 133.813C164.379 133.549 164.637 133.359 164.938 133.244C165.242 133.13 165.582 133.072 165.958 133.072C166.262 133.072 166.542 133.11 166.796 133.185C167.054 133.257 167.283 133.373 167.483 133.534C167.684 133.692 167.854 133.903 167.994 134.168C168.137 134.429 168.246 134.75 168.321 135.129C168.396 135.509 168.434 135.955 168.434 136.467ZM167.435 137.82V136.3C167.435 135.949 167.414 135.641 167.371 135.376C167.331 135.108 167.272 134.879 167.193 134.689C167.115 134.499 167.014 134.345 166.893 134.227C166.774 134.109 166.637 134.023 166.479 133.969C166.325 133.912 166.151 133.883 165.958 133.883C165.722 133.883 165.512 133.928 165.33 134.018C165.147 134.104 164.993 134.241 164.868 134.431C164.746 134.621 164.653 134.87 164.588 135.178C164.524 135.486 164.492 135.86 164.492 136.3V137.82C164.492 138.171 164.511 138.481 164.551 138.75C164.594 139.018 164.656 139.251 164.739 139.448C164.821 139.641 164.921 139.8 165.04 139.926C165.158 140.051 165.294 140.144 165.448 140.205C165.605 140.262 165.779 140.291 165.969 140.291C166.212 140.291 166.425 140.244 166.608 140.151C166.791 140.058 166.943 139.913 167.064 139.716C167.19 139.516 167.283 139.26 167.344 138.948C167.405 138.633 167.435 138.257 167.435 137.82Z" fill="#2196F3"/>
<path d="M54.5591 136.467V137.659C54.5591 138.3 54.5018 138.841 54.3872 139.281C54.2726 139.722 54.1079 140.076 53.8931 140.345C53.6782 140.613 53.4186 140.808 53.1143 140.93C52.8135 141.048 52.4733 141.107 52.0938 141.107C51.793 141.107 51.5155 141.07 51.2612 140.995C51.007 140.919 50.7778 140.799 50.5737 140.635C50.3732 140.466 50.2013 140.248 50.0581 139.979C49.9149 139.711 49.8057 139.385 49.7305 139.002C49.6553 138.619 49.6177 138.171 49.6177 137.659V136.467C49.6177 135.826 49.675 135.289 49.7896 134.855C49.9077 134.422 50.0742 134.075 50.2891 133.813C50.5039 133.549 50.7617 133.359 51.0625 133.244C51.3669 133.13 51.707 133.072 52.083 133.072C52.3874 133.072 52.6667 133.11 52.9209 133.185C53.1787 133.257 53.4079 133.373 53.6084 133.534C53.8089 133.692 53.979 133.903 54.1187 134.168C54.2619 134.429 54.3711 134.75 54.4463 135.129C54.5215 135.509 54.5591 135.955 54.5591 136.467ZM53.5601 137.82V136.3C53.5601 135.949 53.5386 135.641 53.4956 135.376C53.4562 135.108 53.3971 134.879 53.3184 134.689C53.2396 134.499 53.1393 134.345 53.0176 134.227C52.8994 134.109 52.7616 134.023 52.604 133.969C52.45 133.912 52.2764 133.883 52.083 133.883C51.8467 133.883 51.6372 133.928 51.4546 134.018C51.272 134.104 51.118 134.241 50.9927 134.431C50.8709 134.621 50.7778 134.87 50.7134 135.178C50.6489 135.486 50.6167 135.86 50.6167 136.3V137.82C50.6167 138.171 50.6364 138.481 50.6758 138.75C50.7188 139.018 50.7814 139.251 50.8638 139.448C50.9461 139.641 51.0464 139.8 51.1646 139.926C51.2827 140.051 51.4188 140.144 51.5728 140.205C51.7303 140.262 51.904 140.291 52.0938 140.291C52.3372 140.291 52.5503 140.244 52.7329 140.151C52.9155 140.058 53.0677 139.913 53.1895 139.716C53.3148 139.516 53.4079 139.26 53.4688 138.948C53.5296 138.633 53.5601 138.257 53.5601 137.82Z" fill="#2196F3"/>
</g>
<defs>
<filter id="filter0_d_3154_417028" x="0" y="0" width="213" height="213" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3154_417028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3154_417028" result="shape"/>
</filter>
<filter id="filter1_b_3154_417028" x="14" y="10" width="185" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="3"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_3154_417028"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_3154_417028" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

29
ui-ngx/src/assets/widget/simple-gauge/donut-layout.svg

@ -0,0 +1,29 @@
<svg width="213" height="213" viewBox="0 0 213 213" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_3154_417086)">
<rect x="8" y="4" width="197" height="197" rx="4" fill="white" shape-rendering="crispEdges"/>
<g filter="url(#filter1_b_3154_417086)">
<rect x="20" y="16" width="173" height="173" rx="4" fill="white" fill-opacity="0.76"/>
</g>
<path d="M106.5 37.5C141.57 37.5 170 65.9299 170 101C170 136.07 141.57 164.5 106.5 164.5C71.4299 164.5 43 136.07 43 101C43 65.9299 71.4299 37.5 106.5 37.5ZM106.5 144.498C130.523 144.498 149.997 125.023 149.997 101C149.997 76.977 130.523 57.5025 106.5 57.5025C82.477 57.5025 63.0025 76.977 63.0025 101C63.0025 125.023 82.477 144.498 106.5 144.498Z" fill="black" fill-opacity="0.06"/>
<path d="M169.025 89.913C166.406 75.1456 158.645 61.7817 147.117 52.1887C135.588 42.5956 121.036 37.3927 106.039 37.5017L106.184 57.5036C116.457 57.429 126.425 60.993 134.322 67.5642C142.219 74.1355 147.536 83.2897 149.329 93.4054L169.025 89.913Z" fill="#2196F3"/>
<path d="M105.848 109.375V112H92.5039V109.744L98.9844 102.676C99.6953 101.874 100.256 101.181 100.666 100.598C101.076 100.014 101.363 99.4902 101.527 99.0254C101.701 98.5514 101.787 98.0911 101.787 97.6445C101.787 97.0156 101.669 96.4642 101.432 95.9902C101.204 95.5072 100.867 95.1289 100.42 94.8555C99.9733 94.5729 99.431 94.4316 98.793 94.4316C98.0547 94.4316 97.4349 94.5911 96.9336 94.9102C96.4323 95.2292 96.054 95.6712 95.7988 96.2363C95.5436 96.7923 95.416 97.4303 95.416 98.1504H92.1211C92.1211 96.9928 92.3854 95.9355 92.9141 94.9785C93.4427 94.0124 94.2083 93.2467 95.2109 92.6816C96.2135 92.1074 97.4212 91.8203 98.834 91.8203C100.165 91.8203 101.295 92.0436 102.225 92.4902C103.154 92.9368 103.861 93.5703 104.344 94.3906C104.836 95.2109 105.082 96.1816 105.082 97.3027C105.082 97.9225 104.982 98.5378 104.781 99.1484C104.581 99.7591 104.294 100.37 103.92 100.98C103.555 101.582 103.122 102.188 102.621 102.799C102.12 103.4 101.568 104.011 100.967 104.631L96.6602 109.375H105.848ZM122.201 104.904V107.529H107.845L107.736 105.547L116.322 92.0938H118.961L116.103 96.9883L111.168 104.904H122.201ZM119.713 92.0938V112H116.418V92.0938H119.713Z" fill="black" fill-opacity="0.54"/>
</g>
<defs>
<filter id="filter0_d_3154_417086" x="0" y="0" width="213" height="213" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3154_417086"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3154_417086" result="shape"/>
</filter>
<filter id="filter1_b_3154_417086" x="14" y="10" width="185" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="3"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_3154_417086"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_3154_417086" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

31
ui-ngx/src/assets/widget/simple-gauge/horizontal-bar-layout.svg

@ -0,0 +1,31 @@
<svg width="213" height="213" viewBox="0 0 213 213" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_3154_417105)">
<rect x="8" y="4" width="197" height="197" rx="4" fill="white" shape-rendering="crispEdges"/>
<g filter="url(#filter1_b_3154_417105)">
<rect x="20" y="16" width="173" height="173" rx="4" fill="white" fill-opacity="0.76"/>
</g>
<path d="M105.848 81.875V84.5H92.5039V82.2441L98.9844 75.1758C99.6953 74.3737 100.256 73.681 100.666 73.0977C101.076 72.5143 101.363 71.9902 101.527 71.5254C101.701 71.0514 101.787 70.5911 101.787 70.1445C101.787 69.5156 101.669 68.9642 101.432 68.4902C101.204 68.0072 100.867 67.6289 100.42 67.3555C99.9733 67.0729 99.431 66.9316 98.793 66.9316C98.0547 66.9316 97.4349 67.0911 96.9336 67.4102C96.4323 67.7292 96.054 68.1712 95.7988 68.7363C95.5436 69.2923 95.416 69.9303 95.416 70.6504H92.1211C92.1211 69.4928 92.3854 68.4355 92.9141 67.4785C93.4427 66.5124 94.2083 65.7467 95.2109 65.1816C96.2135 64.6074 97.4212 64.3203 98.834 64.3203C100.165 64.3203 101.295 64.5436 102.225 64.9902C103.154 65.4368 103.861 66.0703 104.344 66.8906C104.836 67.7109 105.082 68.6816 105.082 69.8027C105.082 70.4225 104.982 71.0378 104.781 71.6484C104.581 72.2591 104.294 72.8698 103.92 73.4805C103.555 74.082 103.122 74.6882 102.621 75.2988C102.12 75.9004 101.568 76.5111 100.967 77.1309L96.6602 81.875H105.848ZM122.201 77.4043V80.0293H107.845L107.736 78.0469L116.322 64.5938H118.961L116.103 69.4883L111.168 77.4043H122.201ZM119.713 64.5938V84.5H116.418V64.5938H119.713Z" fill="black" fill-opacity="0.54"/>
<path d="M162.916 124.137V132H161.922V125.377L159.918 126.108V125.211L162.76 124.137H162.916ZM170.997 127.467V128.659C170.997 129.3 170.939 129.841 170.825 130.281C170.71 130.722 170.545 131.076 170.331 131.345C170.116 131.613 169.856 131.808 169.552 131.93C169.251 132.048 168.911 132.107 168.531 132.107C168.23 132.107 167.953 132.07 167.699 131.995C167.444 131.919 167.215 131.799 167.011 131.635C166.811 131.466 166.639 131.248 166.496 130.979C166.352 130.711 166.243 130.385 166.168 130.002C166.093 129.619 166.055 129.171 166.055 128.659V127.467C166.055 126.826 166.112 126.289 166.227 125.855C166.345 125.422 166.512 125.075 166.727 124.813C166.941 124.549 167.199 124.359 167.5 124.244C167.804 124.13 168.145 124.072 168.521 124.072C168.825 124.072 169.104 124.11 169.358 124.185C169.616 124.257 169.845 124.373 170.046 124.534C170.246 124.692 170.417 124.903 170.556 125.168C170.699 125.429 170.809 125.75 170.884 126.129C170.959 126.509 170.997 126.955 170.997 127.467ZM169.998 128.82V127.3C169.998 126.949 169.976 126.641 169.933 126.376C169.894 126.108 169.835 125.879 169.756 125.689C169.677 125.499 169.577 125.345 169.455 125.227C169.337 125.109 169.199 125.023 169.042 124.969C168.888 124.912 168.714 124.883 168.521 124.883C168.284 124.883 168.075 124.928 167.892 125.018C167.709 125.104 167.556 125.241 167.43 125.431C167.308 125.621 167.215 125.87 167.151 126.178C167.086 126.486 167.054 126.86 167.054 127.3V128.82C167.054 129.171 167.074 129.481 167.113 129.75C167.156 130.018 167.219 130.251 167.301 130.448C167.384 130.641 167.484 130.8 167.602 130.926C167.72 131.051 167.856 131.144 168.01 131.205C168.168 131.262 168.341 131.291 168.531 131.291C168.775 131.291 168.988 131.244 169.17 131.151C169.353 131.058 169.505 130.913 169.627 130.716C169.752 130.516 169.845 130.26 169.906 129.948C169.967 129.633 169.998 129.257 169.998 128.82ZM177.434 127.467V128.659C177.434 129.3 177.377 129.841 177.262 130.281C177.148 130.722 176.983 131.076 176.768 131.345C176.553 131.613 176.294 131.808 175.989 131.93C175.688 132.048 175.348 132.107 174.969 132.107C174.668 132.107 174.39 132.07 174.136 131.995C173.882 131.919 173.653 131.799 173.449 131.635C173.248 131.466 173.076 131.248 172.933 130.979C172.79 130.711 172.681 130.385 172.605 130.002C172.53 129.619 172.493 129.171 172.493 128.659V127.467C172.493 126.826 172.55 126.289 172.665 125.855C172.783 125.422 172.949 125.075 173.164 124.813C173.379 124.549 173.637 124.359 173.938 124.244C174.242 124.13 174.582 124.072 174.958 124.072C175.262 124.072 175.542 124.11 175.796 124.185C176.054 124.257 176.283 124.373 176.483 124.534C176.684 124.692 176.854 124.903 176.994 125.168C177.137 125.429 177.246 125.75 177.321 126.129C177.396 126.509 177.434 126.955 177.434 127.467ZM176.435 128.82V127.3C176.435 126.949 176.414 126.641 176.371 126.376C176.331 126.108 176.272 125.879 176.193 125.689C176.115 125.499 176.014 125.345 175.893 125.227C175.774 125.109 175.637 125.023 175.479 124.969C175.325 124.912 175.151 124.883 174.958 124.883C174.722 124.883 174.512 124.928 174.33 125.018C174.147 125.104 173.993 125.241 173.868 125.431C173.746 125.621 173.653 125.87 173.588 126.178C173.524 126.486 173.492 126.86 173.492 127.3V128.82C173.492 129.171 173.511 129.481 173.551 129.75C173.594 130.018 173.656 130.251 173.739 130.448C173.821 130.641 173.921 130.8 174.04 130.926C174.158 131.051 174.294 131.144 174.448 131.205C174.605 131.262 174.779 131.291 174.969 131.291C175.212 131.291 175.425 131.244 175.608 131.151C175.791 131.058 175.943 130.913 176.064 130.716C176.19 130.516 176.283 130.26 176.344 129.948C176.405 129.633 176.435 129.257 176.435 128.82Z" fill="#2196F3"/>
<path d="M46.5591 127.467V128.659C46.5591 129.3 46.5018 129.841 46.3872 130.281C46.2726 130.722 46.1079 131.076 45.8931 131.345C45.6782 131.613 45.4186 131.808 45.1143 131.93C44.8135 132.048 44.4733 132.107 44.0938 132.107C43.793 132.107 43.5155 132.07 43.2612 131.995C43.007 131.919 42.7778 131.799 42.5737 131.635C42.3732 131.466 42.2013 131.248 42.0581 130.979C41.9149 130.711 41.8057 130.385 41.7305 130.002C41.6553 129.619 41.6177 129.171 41.6177 128.659V127.467C41.6177 126.826 41.675 126.289 41.7896 125.855C41.9077 125.422 42.0742 125.075 42.2891 124.813C42.5039 124.549 42.7617 124.359 43.0625 124.244C43.3669 124.13 43.707 124.072 44.083 124.072C44.3874 124.072 44.6667 124.11 44.9209 124.185C45.1787 124.257 45.4079 124.373 45.6084 124.534C45.8089 124.692 45.979 124.903 46.1187 125.168C46.2619 125.429 46.3711 125.75 46.4463 126.129C46.5215 126.509 46.5591 126.955 46.5591 127.467ZM45.5601 128.82V127.3C45.5601 126.949 45.5386 126.641 45.4956 126.376C45.4562 126.108 45.3971 125.879 45.3184 125.689C45.2396 125.499 45.1393 125.345 45.0176 125.227C44.8994 125.109 44.7616 125.023 44.604 124.969C44.45 124.912 44.2764 124.883 44.083 124.883C43.8467 124.883 43.6372 124.928 43.4546 125.018C43.272 125.104 43.118 125.241 42.9927 125.431C42.8709 125.621 42.7778 125.87 42.7134 126.178C42.6489 126.486 42.6167 126.86 42.6167 127.3V128.82C42.6167 129.171 42.6364 129.481 42.6758 129.75C42.7188 130.018 42.7814 130.251 42.8638 130.448C42.9461 130.641 43.0464 130.8 43.1646 130.926C43.2827 131.051 43.4188 131.144 43.5728 131.205C43.7303 131.262 43.904 131.291 44.0938 131.291C44.3372 131.291 44.5503 131.244 44.7329 131.151C44.9155 131.058 45.0677 130.913 45.1895 130.716C45.3148 130.516 45.4079 130.26 45.4688 129.948C45.5296 129.633 45.5601 129.257 45.5601 128.82Z" fill="#2196F3"/>
<rect x="44" y="96" width="125" height="20" fill="black" fill-opacity="0.06"/>
<rect x="44" y="96" width="32" height="20" fill="#2196F3"/>
</g>
<defs>
<filter id="filter0_d_3154_417105" x="0" y="0" width="213" height="213" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3154_417105"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3154_417105" result="shape"/>
</filter>
<filter id="filter1_b_3154_417105" x="14" y="10" width="185" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="3"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_3154_417105"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_3154_417105" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

31
ui-ngx/src/assets/widget/simple-gauge/vertical-bar-layout.svg

@ -0,0 +1,31 @@
<svg width="213" height="213" viewBox="0 0 213 213" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_3154_417125)">
<rect x="8" y="4" width="197" height="197" rx="4" fill="white" shape-rendering="crispEdges"/>
<g filter="url(#filter1_b_3154_417125)">
<rect x="20" y="16" width="173" height="173" rx="4" fill="white" fill-opacity="0.76"/>
</g>
<path d="M105.848 53.375V56H92.5039V53.7441L98.9844 46.6758C99.6953 45.8737 100.256 45.181 100.666 44.5977C101.076 44.0143 101.363 43.4902 101.527 43.0254C101.701 42.5514 101.787 42.0911 101.787 41.6445C101.787 41.0156 101.669 40.4642 101.432 39.9902C101.204 39.5072 100.867 39.1289 100.42 38.8555C99.9733 38.5729 99.431 38.4316 98.793 38.4316C98.0547 38.4316 97.4349 38.5911 96.9336 38.9102C96.4323 39.2292 96.054 39.6712 95.7988 40.2363C95.5436 40.7923 95.416 41.4303 95.416 42.1504H92.1211C92.1211 40.9928 92.3854 39.9355 92.9141 38.9785C93.4427 38.0124 94.2083 37.2467 95.2109 36.6816C96.2135 36.1074 97.4212 35.8203 98.834 35.8203C100.165 35.8203 101.295 36.0436 102.225 36.4902C103.154 36.9368 103.861 37.5703 104.344 38.3906C104.836 39.2109 105.082 40.1816 105.082 41.3027C105.082 41.9225 104.982 42.5378 104.781 43.1484C104.581 43.7591 104.294 44.3698 103.92 44.9805C103.555 45.582 103.122 46.1882 102.621 46.7988C102.12 47.4004 101.568 48.0111 100.967 48.6309L96.6602 53.375H105.848ZM122.201 48.9043V51.5293H107.845L107.736 49.5469L116.322 36.0938H118.961L116.103 40.9883L111.168 48.9043H122.201ZM119.713 36.0938V56H116.418V36.0938H119.713Z" fill="black" fill-opacity="0.54"/>
<path d="M124.916 60.1367V68H123.922V61.3774L121.918 62.1079V61.2109L124.76 60.1367H124.916ZM132.997 63.4668V64.6592C132.997 65.3001 132.939 65.8408 132.825 66.2812C132.71 66.7217 132.545 67.0762 132.331 67.3447C132.116 67.6133 131.856 67.8084 131.552 67.9302C131.251 68.0483 130.911 68.1074 130.531 68.1074C130.23 68.1074 129.953 68.0698 129.699 67.9946C129.444 67.9194 129.215 67.7995 129.011 67.6348C128.811 67.4665 128.639 67.248 128.496 66.9795C128.352 66.7109 128.243 66.3851 128.168 66.002C128.093 65.6188 128.055 65.1712 128.055 64.6592V63.4668C128.055 62.8258 128.112 62.2887 128.227 61.8555C128.345 61.4222 128.512 61.0749 128.727 60.8135C128.941 60.5485 129.199 60.3587 129.5 60.2441C129.804 60.1296 130.145 60.0723 130.521 60.0723C130.825 60.0723 131.104 60.1099 131.358 60.1851C131.616 60.2567 131.845 60.373 132.046 60.5342C132.246 60.6917 132.417 60.903 132.556 61.168C132.699 61.4294 132.809 61.7498 132.884 62.1294C132.959 62.509 132.997 62.9548 132.997 63.4668ZM131.998 64.8203V63.3003C131.998 62.9494 131.976 62.6414 131.933 62.3765C131.894 62.1079 131.835 61.8787 131.756 61.689C131.677 61.4992 131.577 61.3452 131.455 61.2271C131.337 61.1089 131.199 61.0229 131.042 60.9692C130.888 60.9119 130.714 60.8833 130.521 60.8833C130.284 60.8833 130.075 60.9281 129.892 61.0176C129.709 61.1035 129.556 61.2414 129.43 61.4312C129.308 61.6209 129.215 61.8698 129.151 62.1777C129.086 62.4857 129.054 62.8599 129.054 63.3003V64.8203C129.054 65.1712 129.074 65.481 129.113 65.7495C129.156 66.0181 129.219 66.2508 129.301 66.4478C129.384 66.6411 129.484 66.8005 129.602 66.9258C129.72 67.0511 129.856 67.1442 130.01 67.2051C130.168 67.2624 130.341 67.291 130.531 67.291C130.775 67.291 130.988 67.2445 131.17 67.1514C131.353 67.0583 131.505 66.9132 131.627 66.7163C131.752 66.5158 131.845 66.2598 131.906 65.9482C131.967 65.6331 131.998 65.2572 131.998 64.8203ZM139.434 63.4668V64.6592C139.434 65.3001 139.377 65.8408 139.262 66.2812C139.148 66.7217 138.983 67.0762 138.768 67.3447C138.553 67.6133 138.294 67.8084 137.989 67.9302C137.688 68.0483 137.348 68.1074 136.969 68.1074C136.668 68.1074 136.39 68.0698 136.136 67.9946C135.882 67.9194 135.653 67.7995 135.449 67.6348C135.248 67.4665 135.076 67.248 134.933 66.9795C134.79 66.7109 134.681 66.3851 134.605 66.002C134.53 65.6188 134.493 65.1712 134.493 64.6592V63.4668C134.493 62.8258 134.55 62.2887 134.665 61.8555C134.783 61.4222 134.949 61.0749 135.164 60.8135C135.379 60.5485 135.637 60.3587 135.938 60.2441C136.242 60.1296 136.582 60.0723 136.958 60.0723C137.262 60.0723 137.542 60.1099 137.796 60.1851C138.054 60.2567 138.283 60.373 138.483 60.5342C138.684 60.6917 138.854 60.903 138.994 61.168C139.137 61.4294 139.246 61.7498 139.321 62.1294C139.396 62.509 139.434 62.9548 139.434 63.4668ZM138.435 64.8203V63.3003C138.435 62.9494 138.414 62.6414 138.371 62.3765C138.331 62.1079 138.272 61.8787 138.193 61.689C138.115 61.4992 138.014 61.3452 137.893 61.2271C137.774 61.1089 137.637 61.0229 137.479 60.9692C137.325 60.9119 137.151 60.8833 136.958 60.8833C136.722 60.8833 136.512 60.9281 136.33 61.0176C136.147 61.1035 135.993 61.2414 135.868 61.4312C135.746 61.6209 135.653 61.8698 135.588 62.1777C135.524 62.4857 135.492 62.8599 135.492 63.3003V64.8203C135.492 65.1712 135.511 65.481 135.551 65.7495C135.594 66.0181 135.656 66.2508 135.739 66.4478C135.821 66.6411 135.921 66.8005 136.04 66.9258C136.158 67.0511 136.294 67.1442 136.448 67.2051C136.605 67.2624 136.779 67.291 136.969 67.291C137.212 67.291 137.425 67.2445 137.608 67.1514C137.791 67.0583 137.943 66.9132 138.064 66.7163C138.19 66.5158 138.283 66.2598 138.344 65.9482C138.405 65.6331 138.435 65.2572 138.435 64.8203Z" fill="#2196F3"/>
<path d="M126.559 157.467V158.659C126.559 159.3 126.502 159.841 126.387 160.281C126.273 160.722 126.108 161.076 125.893 161.345C125.678 161.613 125.419 161.808 125.114 161.93C124.813 162.048 124.473 162.107 124.094 162.107C123.793 162.107 123.515 162.07 123.261 161.995C123.007 161.919 122.778 161.799 122.574 161.635C122.373 161.466 122.201 161.248 122.058 160.979C121.915 160.711 121.806 160.385 121.73 160.002C121.655 159.619 121.618 159.171 121.618 158.659V157.467C121.618 156.826 121.675 156.289 121.79 155.855C121.908 155.422 122.074 155.075 122.289 154.813C122.504 154.549 122.762 154.359 123.062 154.244C123.367 154.13 123.707 154.072 124.083 154.072C124.387 154.072 124.667 154.11 124.921 154.185C125.179 154.257 125.408 154.373 125.608 154.534C125.809 154.692 125.979 154.903 126.119 155.168C126.262 155.429 126.371 155.75 126.446 156.129C126.521 156.509 126.559 156.955 126.559 157.467ZM125.56 158.82V157.3C125.56 156.949 125.539 156.641 125.496 156.376C125.456 156.108 125.397 155.879 125.318 155.689C125.24 155.499 125.139 155.345 125.018 155.227C124.899 155.109 124.762 155.023 124.604 154.969C124.45 154.912 124.276 154.883 124.083 154.883C123.847 154.883 123.637 154.928 123.455 155.018C123.272 155.104 123.118 155.241 122.993 155.431C122.871 155.621 122.778 155.87 122.713 156.178C122.649 156.486 122.617 156.86 122.617 157.3V158.82C122.617 159.171 122.636 159.481 122.676 159.75C122.719 160.018 122.781 160.251 122.864 160.448C122.946 160.641 123.046 160.8 123.165 160.926C123.283 161.051 123.419 161.144 123.573 161.205C123.73 161.262 123.904 161.291 124.094 161.291C124.337 161.291 124.55 161.244 124.733 161.151C124.916 161.058 125.068 160.913 125.189 160.716C125.315 160.516 125.408 160.26 125.469 159.948C125.53 159.633 125.56 159.257 125.56 158.82Z" fill="#2196F3"/>
<rect x="97" y="165" width="101" height="20" transform="rotate(-90 97 165)" fill="black" fill-opacity="0.06"/>
<rect x="97" y="165" width="28" height="20" transform="rotate(-90 97 165)" fill="#2196F3"/>
</g>
<defs>
<filter id="filter0_d_3154_417125" x="0" y="0" width="213" height="213" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3154_417125"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3154_417125" result="shape"/>
</filter>
<filter id="filter1_b_3154_417125" x="14" y="10" width="185" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="3"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_3154_417125"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_3154_417125" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

Loading…
Cancel
Save