Browse Source

Merge branch 'master' into task/4143-rpc-templates-update

pull/11718/head
Max Petrov 1 year ago
committed by GitHub
parent
commit
68e82ca2f6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 13
      application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg
  2. 48
      application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg
  3. 17
      application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg
  4. 10
      application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg
  5. 22
      application/src/main/data/json/system/scada_symbols/cross-pipe.svg
  6. 15
      application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg
  7. 9
      application/src/main/data/json/system/scada_symbols/elevated-tank.svg
  8. 10
      application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg
  9. 13
      application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg
  10. 11
      application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg
  11. 15
      application/src/main/data/json/system/scada_symbols/horizontal-tank.svg
  12. 10
      application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg
  13. 15
      application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg
  14. 15
      application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg
  15. 15
      application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg
  16. 15
      application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg
  17. 8
      application/src/main/data/json/system/scada_symbols/leak-sensor.svg
  18. 4
      application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg
  19. 58
      application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg
  20. 13
      application/src/main/data/json/system/scada_symbols/left-flow-meter.svg
  21. 10
      application/src/main/data/json/system/scada_symbols/left-heat-pump.svg
  22. 2
      application/src/main/data/json/system/scada_symbols/left-motor-pump.svg
  23. 17
      application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg
  24. 48
      application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg
  25. 11
      application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg
  26. 11
      application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg
  27. 13
      application/src/main/data/json/system/scada_symbols/pool.svg
  28. 4
      application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg
  29. 13
      application/src/main/data/json/system/scada_symbols/right-flow-meter.svg
  30. 10
      application/src/main/data/json/system/scada_symbols/right-heat-pump.svg
  31. 2
      application/src/main/data/json/system/scada_symbols/right-motor-pump.svg
  32. 17
      application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg
  33. 2
      application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg
  34. 8
      application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg
  35. 15
      application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg
  36. 15
      application/src/main/data/json/system/scada_symbols/spherical-tank.svg
  37. 15
      application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg
  38. 15
      application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg
  39. 15
      application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg
  40. 15
      application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg
  41. 13
      application/src/main/data/json/system/scada_symbols/top-flow-meter.svg
  42. 50
      application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg
  43. 17
      application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg
  44. 10
      application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg
  45. 13
      application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg
  46. 11
      application/src/main/data/json/system/scada_symbols/vertical-pipe.svg
  47. 15
      application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg
  48. 15
      application/src/main/data/json/system/scada_symbols/vertical-tank.svg
  49. 4
      application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg
  50. 12
      application/src/main/data/json/system/scada_symbols/waterstop.svg
  51. 2
      application/src/main/data/json/system/widget_types/scada_symbol.json
  52. 4
      pom.xml
  53. 49
      ui-ngx/patches/angular-gridster2+15.0.4.patch
  54. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html
  55. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts
  56. 11
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts
  57. 6
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts
  58. 8
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts
  59. 6
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts
  60. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html
  61. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts
  62. 26
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts
  63. 5
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html
  64. 37
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts
  65. 47
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts
  66. 84
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts
  67. 207
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts
  68. 7
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html
  69. 191
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts
  70. 6
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts
  71. 57
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html
  72. 170
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts
  73. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html
  74. 17
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  75. 43
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  76. 30
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts
  77. 11
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts
  78. 36
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts
  79. 4
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  80. 31
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
  81. 43
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts
  82. 8
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts
  83. 3
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  84. 7
      ui-ngx/src/app/shared/import-export/import-export.service.ts
  85. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  86. 8
      ui-ngx/src/assets/locale/locale.constant-lt_LT.json
  87. 386
      ui-ngx/src/assets/locale/locale.constant-nl_BE.json
  88. 8
      ui-ngx/src/assets/locale/locale.constant-pl_PL.json
  89. 1
      ui-ngx/src/assets/metadata/connector-default-configs/modbus.json
  90. 3
      ui-ngx/src/styles.scss

13
application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
"title": "Bottom flow meter",
"description": "Bottom flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [
@ -20,7 +19,7 @@
},
{
"tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
},
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -733,7 +732,8 @@
"step": null
}
]
}]]></tb:metadata><mask id="path-7-inside-1_1253_89545" fill="white">
}]]></tb:metadata>
<mask id="path-7-inside-1_1253_89545" fill="white">
<path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/>
</mask><defs>
<linearGradient id="paint2_linear_1253_89545" x1="155.2" x2="177.6" y1="61" y2="85.4" gradientTransform="translate(0,150)" gradientUnits="userSpaceOnUse">
@ -750,7 +750,7 @@
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/>
<feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/>
</filter>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -781,6 +781,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

48
application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Bottom right elbow pipe",
"description": "Bottom right elbow pipe with fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(-delta, delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n",
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [
{
"tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null
}
]
}]]></tb:metadata><g transform="rotate(-90,100,100)" clip-path="url(#clip0_1245_66459)">
}]]></tb:metadata>
<g transform="rotate(-90,100,100)" clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
@ -530,7 +499,12 @@
<clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/>
<pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<pattern id="base-vertical-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(135)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="136" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(-90)" x="-186" y="64" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m136.05 136-72.05 0.1c0.09-0.87135 0.23117-2.1615 0.44675-3.6113 0.48513-3.2616 1.3459-7.8299 2.8468-13.038 3.0077-10.437 8.5533-23.332 18.689-33.468 10.136-10.136 23.03-15.682 33.467-18.689 5.2088-1.5014 9.7767-2.3619 13.039-2.847 1.4491-0.21538 2.6909-0.35652 3.5618-0.44748z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(-.99929 0 0 -.99919 199.91 199.9)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" tb:tag="center-fluid-background" style=""/>
<path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.142 17.939-50.576 17.628-50.576 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

17
application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Bottom tee pipe",
"description": "Bottom tee pipe with configurable left/right/bottom fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n",
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [
{
"tag": "bottom-fluid",
@ -576,7 +575,8 @@
"step": null
}
]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66617)">
}]]></tb:metadata>
<g clip-path="url(#clip0_1245_66617)">
<path d="m14 64h172v72h-172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m14 64h172v72h-172z" fill="url(#paint0_linear_1245_66617)" style="fill:url(#paint0_linear_1245_66617)"/>
<path d="m15.5 65.5h169v69h-169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -738,9 +738,12 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
<clipPath id="clip0_1245_66617">
<rect width="200" height="200" fill="#fff"/>
</clipPath>

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

10
application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
"title": "Centrifugal pump",
"description": "Centrifugal pump with configurable connectors, running animation and various states.",
"searchTags": [
@ -11,12 +10,12 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": null
},
{
"tag": "center",
"stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n",
"stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar centerRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!centerRotate) {\n centerRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n centerRotate.speed(speed).play();\n }\n} else {\n if (centerRotate) {\n centerRotate.pause();\n }\n}\n",
"actions": null
},
{
@ -415,7 +414,8 @@
"step": null
}
]
}]]></tb:metadata><rect x="50" y="371" width="300" height="29" rx="7" fill="#fff"/><rect x="50" y="371" width="300" height="29" rx="7" fill="url(#paint0_linear_1481_75415)"/><rect x="51.5" y="372.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m103 274h194l32 100h-258l32-100z" fill="#fff"/><path d="m103 274h194l32 100h-258l32-100z" fill="url(#paint1_linear_1481_75415)"/><path d="m73.055 372.5 31.04-97h191.81l31.04 97h-253.89z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="rightTopConnector">
}]]></tb:metadata>
<rect x="50" y="371" width="300" height="29" rx="7" fill="#fff"/><rect x="50" y="371" width="300" height="29" rx="7" fill="url(#paint0_linear_1481_75415)"/><rect x="51.5" y="372.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m103 274h194l32 100h-258l32-100z" fill="#fff"/><path d="m103 274h194l32 100h-258l32-100z" fill="url(#paint1_linear_1481_75415)"/><path d="m73.055 372.5 31.04-97h191.81l31.04 97h-253.89z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="rightTopConnector">
<rect x="200" y="64" width="186" height="72" fill="#fff"/>
<rect x="200" y="64" width="186" height="72" fill="url(#paint2_linear_1481_75415)"/>
<rect x="201.5" y="65.5" width="183" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

22
application/src/main/data/json/system/scada_symbols/cross-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Cross pipe",
"description": "Cross pipe with configurable left/right/top/bottom fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n",
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [
{
"tag": "bottom-fluid",
@ -741,7 +740,8 @@
"step": null
}
]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66442)">
}]]></tb:metadata>
<g clip-path="url(#clip0_1245_66442)">
<path d="m64 186v-172h72v172h-72z" fill="#fff" tb:tag="pipe-background"/>
<path d="m64 186v-172h72v172h-72z" fill="url(#paint0_linear_1245_66442)"/>
<path d="m65.5 184.5v-169h69v169h-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -793,7 +793,7 @@
<clipPath id="clip0_1245_66442">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -1013,10 +1013,14 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="left-fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="top-fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="bottom-fluid-background"/><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="right-fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#left-liquid)" stroke-width="0" style="display: none;" tb:tag="left-fluid"/><rect x="136" y="64" width="50" height="72" fill="url(#right-liquid)" stroke-width="0" style="display: none;" tb:tag="right-fluid"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="url(#top-liquid)" stroke-width="0" style="display: none;" tb:tag="top-fluid"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="url(#bottom-liquid)" stroke-width="0" style="display: none;" tb:tag="bottom-fluid"/><g transform="translate(64 64)" style="display: none;" tb:tag="overlay">
<path d="m0 72 36-36-36-36z" fill="#fff" style=""/>
<path d="m0 72 36-36-36-36z" fill="url(#paint0_linear_1281_37969)" style="fill: url(&quot;#paint0_linear_1281_37969&quot;);"/>

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 60 KiB

15
application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"><tb:metadata xmlns=""><![CDATA[{
"title": "Cylindrical tank",
"description": "Cylindrical tank with current volume value and level visualizations.",
"searchTags": [
@ -24,17 +23,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -560,7 +559,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white">
}]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -653,7 +653,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -801,6 +801,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

9
application/src/main/data/json/system/scada_symbols/elevated-tank.svg

@ -24,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -900,7 +900,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -1048,6 +1048,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

10
application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal ball valve",
"description": "Horizontal ball valve with open/close animation and state colors.",
"searchTags": [
@ -11,7 +10,7 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null
},
{
@ -25,7 +24,7 @@
},
{
"tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originX: 100});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle, originX: 100});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originX: 100});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle, originX: 100});\n element.remember('openAnimate', false);\n}\n",
"actions": null
}
],
@ -200,7 +199,8 @@
"step": null
}
]
}]]></tb:metadata><defs>
}]]></tb:metadata>
<defs>
<linearGradient id="paint0_linear_1595_99338" x1="58.72" x2="58.3" y1="46.5" y2="153.51" gradientTransform="translate(-1.1e-6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".18316"/>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

13
application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal inline flow meter",
"description": "Horizontal inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [
@ -20,7 +19,7 @@
},
{
"tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
},
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -733,8 +732,9 @@
"step": null
}
]
}]]></tb:metadata><defs>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
}]]></tb:metadata>
<defs>
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -765,6 +765,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

11
application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal pipe",
"description": "Horizontal pipe with fluid and leak visualizations.",
"searchTags": [
@ -11,7 +10,7 @@
"tags": [
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -240,7 +239,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m186 136h-172v-72h172z" fill="#fff" tb:tag="pipe-background"/><path d="m186 136h-172v-72h172z" fill="url(#paint0_linear_955_27716)"/><g stroke-width="3">
}]]></tb:metadata>
<path d="m186 136h-172v-72h172z" fill="#fff" tb:tag="pipe-background"/><path d="m186 136h-172v-72h172z" fill="url(#paint0_linear_955_27716)"/><g stroke-width="3">
<path d="m184.5 134.5h-169v-69h169z" stroke="#000" stroke-opacity=".12"/>
<rect transform="scale(-1)" x="-198.5" y="-148.5" width="11.286" height="97" rx="5.6429" fill="#d9d9d9" stroke="#727171"/>
<rect transform="scale(-1)" x="-12.5" y="-148.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36 6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

15
application/src/main/data/json/system/scada_symbols/horizontal-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="600" fill="none" version="1.1" viewBox="0 0 1e3 600">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="600" fill="none" version="1.1" viewBox="0 0 1e3 600"><tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal tank",
"description": "Horizontal tank with current volume value and level visualizations.",
"searchTags": [
@ -24,17 +23,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -555,7 +554,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse">
}]]></tb:metadata>
<path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m178.68 16h654.82c117.67 0 151.5 98.586 151.5 184.16v204.18c0 135.62-68.502 181.66-162.5 181.66h-627.98c-129 0-177.5-66.558-177.5-181.66s0.0023-129.32 0-212.19c-3e-4 -9.509-5.4994-176.16 161.66-176.16z" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1694_158298)">
<rect transform="scale(1,-1)" x="9" y="-585" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -675,7 +675,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -823,6 +823,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

10
application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal wheel valve",
"description": "Horizontal wheel valve with open/close animation and state colors.",
"searchTags": [
@ -11,7 +10,7 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null
},
{
@ -25,7 +24,7 @@
},
{
"tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n",
"actions": null
}
],
@ -200,7 +199,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m200 29.489c-28.161 0-44.15 0.82311-72 5-28.662 4.2988-72 17-72 17v-13.99c0-4.4182-3.5817-8-8-8h-26c-4.4183 0-8 3.5818-8 8v126c0 4.418 3.5817 8 8 8h26c4.4183 0 8-3.582 8-8v-14.01s43.238 12.711 72 17c27.834 4.151 43.859 5.195 72 5 28.16-0.19499 44.15-1.823 72-6 28.663-4.299 72-16 72-16v14.01c0 4.418 3.582 8 8 8h26c4.418 0 8-3.582 8-8v-126c0-4.4182-3.582-8-8-8h-26c-4.418 0-8 3.5818-8 8v13.99s-43.337-12.701-72-17c-27.85-4.1769-43.839-5-72-5z" fill="#1c943e" tb:tag="background"/><rect x="1.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="14" y="30" width="42" height="142" rx="8" fill="url(#paint0_linear_1343_53957)" style="fill:url(#paint0_linear_1343_53957)"/><rect x="15.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m56 51.988s43.338-12.701 72-17c27.85-4.1769 43.839-5 72-5s44.15 0.8231 72 5c28.663 4.2988 72 17 72 17v98s-43.337 11.701-72 16c-27.85 4.177-43.84 5.806-72 6-28.141 0.195-44.166-0.849-72-5-28.762-4.289-72-17-72-17z" fill="url(#paint1_linear_1343_53957)" style="fill:url(#paint1_linear_1343_53957)"/><path d="m341.67 52.876c0.314 0.09 0.591 0.1699 0.831 0.239v95.722c-0.233 0.062-0.501 0.133-0.802 0.212-1.238 0.328-3.04 0.801-5.292 1.382-4.505 1.163-10.809 2.758-18.008 4.487-14.409 3.46-32.357 7.448-46.62 9.587-27.798 4.169-43.715 5.789-71.788 5.983-28.068 0.194-44.015-0.844-71.769-4.983-14.3-2.133-32.237-6.366-46.632-10.078-7.1905-1.854-13.484-3.574-17.98-4.83-2.2476-0.629-4.0454-1.141-5.2807-1.496-0.3128-0.09-0.5896-0.17-0.8284-0.239v-95.747c0.2396-0.0691 0.5174-0.149 0.8314-0.239 1.2379-0.3548 3.0393-0.8668 5.2909-1.4947 4.5034-1.2558 10.806-2.9751 18.003-4.8284 14.408-3.7103 32.345-7.9439 46.596-10.081 27.761-4.1635 43.674-4.9834 71.778-4.9834s44.017 0.8199 71.778 4.9834c14.251 2.1374 32.189 6.371 46.596 10.081 7.197 1.8533 13.5 3.5726 18.004 4.8284 2.251 0.6279 4.053 1.1399 5.291 1.4947z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="344" y="30" width="42" height="142" rx="8" fill="url(#paint2_linear_1343_53957)" style="fill:url(#paint2_linear_1343_53957)"/><rect x="345.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><g tb:tag="wheel">
}]]></tb:metadata>
<path d="m200 29.489c-28.161 0-44.15 0.82311-72 5-28.662 4.2988-72 17-72 17v-13.99c0-4.4182-3.5817-8-8-8h-26c-4.4183 0-8 3.5818-8 8v126c0 4.418 3.5817 8 8 8h26c4.4183 0 8-3.582 8-8v-14.01s43.238 12.711 72 17c27.834 4.151 43.859 5.195 72 5 28.16-0.19499 44.15-1.823 72-6 28.663-4.299 72-16 72-16v14.01c0 4.418 3.582 8 8 8h26c4.418 0 8-3.582 8-8v-126c0-4.4182-3.582-8-8-8h-26c-4.418 0-8 3.5818-8 8v13.99s-43.337-12.701-72-17c-27.85-4.1769-43.839-5-72-5z" fill="#1c943e" tb:tag="background"/><rect x="1.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="14" y="30" width="42" height="142" rx="8" fill="url(#paint0_linear_1343_53957)" style="fill:url(#paint0_linear_1343_53957)"/><rect x="15.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m56 51.988s43.338-12.701 72-17c27.85-4.1769 43.839-5 72-5s44.15 0.8231 72 5c28.663 4.2988 72 17 72 17v98s-43.337 11.701-72 16c-27.85 4.177-43.84 5.806-72 6-28.141 0.195-44.166-0.849-72-5-28.762-4.289-72-17-72-17z" fill="url(#paint1_linear_1343_53957)" style="fill:url(#paint1_linear_1343_53957)"/><path d="m341.67 52.876c0.314 0.09 0.591 0.1699 0.831 0.239v95.722c-0.233 0.062-0.501 0.133-0.802 0.212-1.238 0.328-3.04 0.801-5.292 1.382-4.505 1.163-10.809 2.758-18.008 4.487-14.409 3.46-32.357 7.448-46.62 9.587-27.798 4.169-43.715 5.789-71.788 5.983-28.068 0.194-44.015-0.844-71.769-4.983-14.3-2.133-32.237-6.366-46.632-10.078-7.1905-1.854-13.484-3.574-17.98-4.83-2.2476-0.629-4.0454-1.141-5.2807-1.496-0.3128-0.09-0.5896-0.17-0.8284-0.239v-95.747c0.2396-0.0691 0.5174-0.149 0.8314-0.239 1.2379-0.3548 3.0393-0.8668 5.2909-1.4947 4.5034-1.2558 10.806-2.9751 18.003-4.8284 14.408-3.7103 32.345-7.9439 46.596-10.081 27.761-4.1635 43.674-4.9834 71.778-4.9834s44.017 0.8199 71.778 4.9834c14.251 2.1374 32.189 6.371 46.596 10.081 7.197 1.8533 13.5 3.5726 18.004 4.8284 2.251 0.6279 4.053 1.1399 5.291 1.4947z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="344" y="30" width="42" height="142" rx="8" fill="url(#paint2_linear_1343_53957)" style="fill:url(#paint2_linear_1343_53957)"/><rect x="345.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><g tb:tag="wheel">
<path d="m300 100c0 55.229-44.772 100-100 100s-100-44.771-100-100c0-55.228 44.772-100 100-100s100 44.772 100 100zm-175.01 0c0 41.428 33.584 75.012 75.012 75.012s75.012-33.584 75.012-75.012-33.584-75.012-75.012-75.012-75.012 33.584-75.012 75.012z" fill="url(#paint3_radial_1343_53957)" style="fill:url(#paint3_radial_1343_53957)"/>
<path d="m194.12 74.998c-1.726 0-3.125-1.3991-3.125-3.125v-46.625c0-3.4517 2.798-6.2499 6.25-6.2499h5.5c3.452 0 6.25 2.7982 6.25 6.2499v46.625c0 1.7259-1.399 3.125-3.125 3.125z" fill="url(#paint4_linear_1343_53957)" style="fill:url(#paint4_linear_1343_53957)"/>
<path d="m222.98 88.918c-0.612-1.6137 0.2-3.418 1.814-4.0301l43.595-16.534c3.227-1.2241 6.836 0.3999 8.06 3.6273l2.217 5.8439c1.224 3.2274-0.4 6.8361-3.628 8.0602l-43.595 16.535c-1.613 0.612-3.418-0.2-4.03-1.814l-4.433-11.688z" fill="url(#paint5_linear_1343_53957)" style="fill:url(#paint5_linear_1343_53957)"/>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

15
application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{
"title": "Large cylindrical tank",
"description": " Large cylindrical tank with current volume value and level visualizations.",
"searchTags": [
@ -24,17 +23,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -560,7 +559,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white">
}]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -660,7 +660,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -808,6 +808,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

15
application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"><tb:metadata xmlns=""><![CDATA[{
"title": "Large stand cylindrical tank",
"description": "Large stand cylindrical tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -561,7 +560,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white">
}]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -661,7 +661,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -809,6 +809,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

15
application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"><tb:metadata xmlns=""><![CDATA[{
"title": "Large stand vertical tank",
"description": "Large stand tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -561,7 +560,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white">
}]]></tb:metadata>
<path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -663,7 +663,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -811,6 +811,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

15
application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{
"title": "Large vertical tank",
"description": "Large vertical tank with current volume value and level visualizations.",
"searchTags": [
@ -24,17 +23,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -560,7 +559,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white">
}]]></tb:metadata>
<path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -662,7 +662,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -810,6 +810,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

8
application/src/main/data/json/system/scada_symbols/leak-sensor.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="100" fill="none" version="1.1" viewBox="0 0 100 100">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="100" fill="none" version="1.1" viewBox="0 0 100 100"><tb:metadata xmlns=""><![CDATA[{
"title": "Leak sensor",
"description": "Leak sensor",
"searchTags": [
@ -24,7 +23,7 @@
},
{
"tag": "icon",
"stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.resetAnimation(element);\n}\n",
"stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.finishCssAnimation(element);\n}\n",
"actions": null
}
],
@ -135,7 +134,8 @@
"step": null
}
]
}]]></tb:metadata><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="#fff" tb:tag="background"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint0_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint1_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" stroke="#fff"/><path d="m56.668 52.309c0 2.04-0.7902 3.9966-2.1967 5.4391-0.7985 0.819-1.7586 1.4347-2.8035 1.8135m10-6.7401c0-10.962-11.667-19.487-11.667-19.487s-11.667 8.5256-11.667 19.487c0 3.2301 1.2292 6.3283 3.4171 8.6125 2.1879 2.284 5.1556 3.5659 8.2497 3.5659 3.0942 0 6.0615-1.2826 8.2493-3.5666 2.188-2.2842 3.4172-5.3817 3.4172-8.6118z" stroke="#1C943E" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" tb:tag="icon"/><path d="m33.631 0s-33.631 0-33.631 16.75v82.09c0 0.6628 0.89543 1.1601 2 1.1601h96c1.1045 0 2-0.4973 2-1.1601v-82.09c0-16.75-33.035-16.75-33.035-16.75h-16.965zm33.535 20.3c-0.64434 0-1.1666 0.3134-1.1666 0.7v75.1c0 0.3866 0.52234 0.7 1.1666 0.7h7.3336c0.64434 0 1.1666-0.3134 1.1666-0.7v-75.1c0-0.3866-0.52233-0.7-1.1666-0.7z" fill="#000" fill-opacity="0" style="stroke-width:.059761" tb:tag="clickArea"/><defs>
}]]></tb:metadata>
<rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="#fff" tb:tag="background"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint0_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint1_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" stroke="#fff"/><path d="m56.668 52.309c0 2.04-0.7902 3.9966-2.1967 5.4391-0.7985 0.819-1.7586 1.4347-2.8035 1.8135m10-6.7401c0-10.962-11.667-19.487-11.667-19.487s-11.667 8.5256-11.667 19.487c0 3.2301 1.2292 6.3283 3.4171 8.6125 2.1879 2.284 5.1556 3.5659 8.2497 3.5659 3.0942 0 6.0615-1.2826 8.2493-3.5666 2.188-2.2842 3.4172-5.3817 3.4172-8.6118z" stroke="#1C943E" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" tb:tag="icon"/><path d="m33.631 0s-33.631 0-33.631 16.75v82.09c0 0.6628 0.89543 1.1601 2 1.1601h96c1.1045 0 2-0.4973 2-1.1601v-82.09c0-16.75-33.035-16.75-33.035-16.75h-16.965zm33.535 20.3c-0.64434 0-1.1666 0.3134-1.1666 0.7v75.1c0 0.3866 0.52234 0.7 1.1666 0.7h7.3336c0.64434 0 1.1666-0.3134 1.1666-0.7v-75.1c0-0.3866-0.52233-0.7-1.1666-0.7z" fill="#000" fill-opacity="0" style="stroke-width:.059761" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217648" x1="100" x2="1.5002" y1="50.119" y2="49.881" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/>

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

4
application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg

@ -19,7 +19,7 @@
},
{
"tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -47,7 +47,7 @@
},
{
"tag": "pointer",
"stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.animate(element, 1000).ease('-').transform({rotate: degrees});\n}",
"stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.cssAnimate(element, 500).ease('ease-in').transform({rotate: degrees});\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

58
application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Left bottom elbow pipe",
"description": "Left bottom elbow pipe with fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(-delta, delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n",
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [
{
"tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null
}
]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66459)">
}]]></tb:metadata>
<g clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
@ -530,12 +499,17 @@
<clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m64 136.15-0.1-72.05c0.87135 0.09 2.1615 0.23117 3.6113 0.44675 3.2616 0.48513 7.8299 1.3459 13.038 2.8468 10.437 3.0077 23.332 8.5533 33.468 18.689 10.136 10.136 15.682 23.03 18.689 33.467 1.5014 5.2088 2.3619 9.7767 2.847 13.039 0.21538 1.4491 0.35652 2.6909 0.44748 3.5618z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(0 -.99929 .99918 0 .25495 199.91)" style="display: none;" tb:tag="center-fluid">
<path d="m63.856 63.897 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" style="" tb:tag="center-fluid-background"/>
<path d="m132.06 63.6s-0.0216 31.824-17.622 50.677c-17.228 18.043-50.883 17.636-50.883 17.636" fill="none" stroke="#727171" stroke-width="7.9" style=""/>
<path d="m63.955 63.797v-0.20016h7.8565v8.1064h-8.1567v-7.9062z" style="fill: rgb(114, 113, 113); stroke-width: 1.0035;"/>
</g><g transform="translate(-1e-4,3e-4)" style="display: none;" tb:tag="leak">
<pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<pattern id="base-vertical-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(135)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m64 136 72.05 0.1c-0.09-0.87135-0.23117-2.1615-0.44675-3.6113-0.48513-3.2616-1.3459-7.8299-2.8468-13.038-3.0077-10.437-8.5533-23.332-18.689-33.468-10.136-10.136-23.03-15.682-33.467-18.689-5.2088-1.5014-9.7767-2.3619-13.039-2.847-1.4491-0.21538-2.6909-0.35652-3.5618-0.44748z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(0 -.99929 .99919 0 .27925 199.93)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" tb:tag="center-fluid-background" style=""/>
<path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.142 17.939-50.576 17.628-50.576 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>
<path d="m63.956 63.954v-0.15398h7.8942v8.05h-8.0503v-7.896z" style="fill: rgb(114, 113, 113);"/>
</g><g transform="rotate(-90 100 100)" style="display: none;" tb:tag="leak">
<path d="m62.784 80.767c0.417 0.1391 0.9517 0.2463 1.3259 0.4603 1.326 0.4603 2.5556 1.2628 3.3039 2.386 0.2779 0.5563 0.5129 1.1873 0.6731 1.7756 0.4915 1.5723 1.0257 3.0697 1.5599 4.5672 0.545 1.3049 1.0792 2.8023 0.544 4.0855-0.1925-0.0107-0.3101 0.0213-0.5026 0.0105 0.3307 2.3744 2.2423 0.7354 6.5024 7.0127 2.2452 8.828 4.8752 21.579 4.8212 23.236-0.0323 0.578 0.0531 1.123 0.0636 1.626-0.011 0.887-0.0969 1.732-0.3752 2.566-0.1499 0.61-0.4924 1.209-0.7172 1.775-0.6315 1.626-1.1134 3.337-1.253 5.144 0.3745-0.481 0.749-0.962 1.1234-1.443 0 0 0.353-0.096 0.2353-0.064-0.1069-0.161 0.0216-0.385 0.182-0.492 0.3527 0.599 0.7055 1.198 0.9833 1.754 1.6139 2.91 3.185 5.894 4.478 9.017-0.7155-3.091-1.8588-6.128-3.2374-9.102-0.3099-0.674-0.6626-1.273-0.8121-2.054-0.4378-1.839 0.4398-3.721 1.4243-5.443l0.0428-0.075c0.0856-0.149 0.214-0.374 0.4065-0.363 0.2354-0.064 0.4919 0.182 0.6737 0.385 0.8659 1.091 1.7746 2.108 2.6833 3.124 1.1653 1.262 2.459 2.3 3.5814 3.637 1.0156 1.177 1.8708 2.461 2.6511 3.702 0.3527 0.599 0.7482 1.123 0.8656 1.786 0.1923 0.706 0.192 1.401 0.0742 2.128-0.1287 0.92-0.2146 1.765-0.4181 2.642-1.0925 5.036 4.1863 14.098 2.2917 18.974 1.1129-1.946 2.1938-4.009 3.3067-5.955 0.4161 2.919 1.6132 5.69 2.7672 8.535 0.342 0.791 0.684 1.583 0.865 2.481 0.192 0.706 0.224 1.519 0.224 2.214 0 1.391-0.119 2.813-0.878 3.968 0.577-0.663 1.006-1.412 1.316-2.128 0.182 0.203 0.438 0.449 0.62 0.652 1.604 1.712 3.25 3.349 4.779 5.018-0.374-0.909-0.984-1.754-1.636-2.525-0.908-1.016-2.063-1.776-2.651-3.006-0.695-1.39-0.448-3.037-0.629-4.631-0.245-2.524-1.464-4.909-2.49-7.284-1.026-2.374-2.4215-4.17-3.795-8.188-0.9898-2.895-2.4972-7.611-1.9831-9.9 0.1179-0.727 0.3534-1.486 0.6638-2.203 0.182-0.492 0.4389-0.941 0.7385-1.465 0.3424-0.599 0.642-1.123 0.9096-1.764 0.2141-0.374 0.3532-0.791 0.3748-1.176 0.0643-0.46-0.1387-0.974-0.3097-1.369-0.577-1.423-1.1541-2.846-1.7311-4.268 0.0641 0.235 0.1602 0.588 0.2243 0.823 0.5127 1.883 0.9506 3.722 0.6933 5.562-0.0216 0.385-0.1607 0.802-0.4068 1.058-0.0428 0.075-0.0856 0.15-0.1605 0.107-0.3637 0.289-1.0909 0.171-1.1442-0.257-0.5668-0.224-1.112-0.834-1.4754-1.241-2.6726-3.316-5.3023-6.707-7.8572-10.055-0.2886-0.364-0.5773-0.728-0.7803-1.241-0.203-0.514-0.2563-0.942-0.2669-1.444-0.2982-3.647-2.4962-15.905-3.458-18.422-1.5388-4.028-0.5464-4.267-2.5593-5.9434 0.0535-0.2674-6.9427-5.6276-6.8891-5.8949 0.3322-1.7966 1.584-3.4646 2.7823-4.8651 1.2483-1.1574 5.2035 1.7887 5.8127 1.8117 0.6917 0.0262 13.721 15.05 14.606 16.356 0.3527 0.599 0.2189 1.786 0.4355 2.02 0.0765 0.083 1.5882 0.445 0.3927-0.21-0.023 0.609 1.7337 6.612 1.497 10.847-0.0216 0.385-0.0859 0.845-0.2251 1.262-0.0535 0.268-0.1819 0.492-0.3852 0.674-0.0428 0.075-0.1605 0.107-0.2781 0.139-0.1177 0.032-0.1497-0.086-0.2246-0.129-2.6943-2.236-5.2814-5.006-8.8318-5.746 0.1069 0.161 0.1817 0.204 0.2886 0.364 0.0749 0.043 0.1069 0.161 0.2566 0.246 1.3898 1.391 3.261 2.461 4.3728 3.991 0.5024 0.684 0.962 1.444 1.6677 1.947 0.9302 0.631 2.3097 0.824 3.0367 1.637 0.3635 0.407 0.6414 0.963 0.7695 1.433 0.3525 1.295 0.4269 2.728 0.897 3.99 0.2351 0.631 0.6627 1.273 0.8974 1.904 0.032 0.118 0.107 0.16 0.139 0.278 0.577 1.423 3.497 6.167 3.411 7.707 0.075-1.348-2.706-5.814-2.438-7.151 1.465 1.434 2.855 2.825 4.234 4.408-0.919-1.519-1.999-2.931-3.239-4.236-0.62-0.653-1.24-1.305-1.7-2.065-0.8443-1.476-0.6511-3.551 0.825-4.395 0.161-0.107 0.321-0.214 0.513-0.203 0.985-0.331 2.097-0.192 3.134-0.095 3.027 0.439 5.893 0.986 8.908 1.618 2.214 0.471 4.385 1.017 6.288 2.205 0.15 0.086 0.257 0.246 0.171 0.396 0.599-0.353 1.241-0.085 1.797 0.332 0.598 0.342 1.111 0.835 1.667 1.252 0.482 0.374 0.963 0.749 1.444 1.123 2.256 1.787 4.469 3.649 6.479 5.692-0.78-1.241-1.796-2.418-3.058-3.338-0.887-0.706-1.86-1.262-2.63-2.001-0.844-0.781-1.368-1.775-2.213-2.556-0.406-0.332-0.887-0.707-1.251-1.113 1.123-0.053 2.203-0.031 3.326-0.084-0.802-0.161-1.604-0.322-2.406-0.483-0.994-0.171-1.946-0.417-2.898-0.664-2.352-0.749-4.544-2.301-6.993-2.708-0.193-0.011-0.385-0.021-0.578-0.032 0.107 0.16 0.257 0.246 0.364 0.406-0.118 0.033-0.268-0.053-0.385-0.021-2.161-0.044-4.374-0.515-6.427-1.093-1.829-0.45-3.807-0.985-5.005-2.365-0.363-0.407-0.684-0.888-1.047-1.295-0.364-0.406-1.0054-0.674-1.5508-0.589 0.2575-2.534 1.0086-5.312-0.4558-7.441-1.1438-1.647-0.6523-3.686-2.1639-4.048 0.5862 0.632 0.9484-0.88 0.3852-2.121-0.5632-1.242-11.812-19.078-21.406-19.814-1.5082 0.7267-2.7386 2.0097-3.3594 3.4425-0.0428 0.0749-0.0428 0.0749-0.0856 0.1497-0.951-2.3317-1.9341-4.7812-3.2381-7.0168-0.4275-0.6418-0.8123-1.3585-1.4431-1.8186-1.2832-0.5352-2.898-0.6641-4.438-0.7501z" clip-path="url(#clipPath4310)" fill="#5c5a5a" style=""/>
<path d="m96.836 106.97c-0.1607 0.802-0.3972 1.217-0.1729 2.041 0.4486 1.647 1.0572 2.066 1.7411 3.649 0.6732 1.776 0.6698 2.683 0.7546 4.619 0.0211 1.005-0.0327 1.968-0.1614 2.887 0.7808-0.149 1.8421 1.436 2.3871 2.046 0.545 0.609 1.005 1.369 1.722 1.679 0.791 0.353 1.646 0.247 2.491 0.333 0.92 0.128 1.786 0.524 2.695 0.845 1.861 0.568 3.797 0.483 5.7 0.28-0.075-0.042-0.075-0.042-0.149-0.085-2.161-0.044-4.374-0.515-6.428-1.093-1.828-0.45-3.807-0.985-5.004-2.365-0.363-0.407-0.684-0.888-1.048-1.295-0.363-0.406-1.328-1.638-1.8737-1.553 0.2575-2.534 0.8497-4.297-0.615-6.426-1.1437-1.647-0.9919-2.182-1.5794-4.108-0.2138-0.321-0.374-0.909-0.4594-1.454z" clip-path="url(#clipPath4304)" fill="#8b8b8b" style=""/>
<path d="m87.799 115.57c0.1069 0.161 0.1818 0.204 0.2887 0.364 0.0748 0.043 0.1069 0.161 0.2566 0.246l0.0748 0.043c0.5667 0.225 1.1335 0.449 1.6895 0.867 0.4491 0.257 0.9302 0.631 1.3365 0.963 1.7748 1.412 3.2928 3.273 5.3993 4.279 0.2997-0.523 0.5993-1.047 0.7064-1.582-0.0535 0.267-0.182 0.492-0.3852 0.674-0.0428 0.074-0.1605 0.106-0.2781 0.138-0.1177 0.033-0.1497-0.085-0.2246-0.128-2.6835-2.429-5.3135-5.125-8.8639-5.864z" clip-path="url(#clipPath4298)" fill="#8b8b8b" style=""/>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

13
application/src/main/data/json/system/scada_symbols/left-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
"title": "Left flow meter",
"description": "Left flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [
@ -20,7 +19,7 @@
},
{
"tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
},
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -733,7 +732,8 @@
"step": null
}
]
}]]></tb:metadata><mask id="path-7-inside-1_1253_89545" fill="white">
}]]></tb:metadata>
<mask id="path-7-inside-1_1253_89545" fill="white">
<path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/>
</mask><defs>
<linearGradient id="paint2_linear_1253_89545" x1="155.2" x2="177.6" y1="61" y2="85.4" gradientTransform="translate(-75,75)" gradientUnits="userSpaceOnUse">
@ -888,7 +888,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,24)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,24)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -919,6 +919,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
</defs><rect transform="rotate(-90)" x="-386" y="264" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect transform="rotate(-90)" x="-386" y="264" width="372" height="72" fill="url(#paint0_linear_1310_41909)" style="fill:url(#paint0_linear_1310_41909)"/><rect transform="rotate(-90)" x="-384.5" y="265.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(90)" x="14" y="-336" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-336" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="m236 164h28.421l31.579 36-31.579 36h-28.421z" fill="#fff" tb:tag="pipe-background"/><path d="m236 164h28.421l31.579 36-31.579 36h-28.421z" fill="url(#paint1_linear_1310_41909)" style="fill:url(#paint1_linear_1310_41909)"/><path d="m237.5 165.5h26.242l30.263 34.5-30.263 34.5h-26.242z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(-90)" x="-398.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(-90)" x="-12.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path transform="translate(-75,75)" d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4a4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="125" cy="200" r="111" fill="#fff" tb:tag="background"/><rect x="44.5" y="180.5" width="161" height="39" rx="3.8571" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m48.36 179c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="91" cy="148" r="16" fill="url(#paint2_linear_1253_89545)" style="fill:url(#paint2_linear_1253_89545)"/><circle cx="91" cy="148" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="159" cy="148" r="16" fill="url(#paint3_linear_1253_89545)" style="fill:url(#paint3_linear_1253_89545)"/><circle cx="159" cy="148" r="15.5" stroke="#000" stroke-opacity=".12"/><text x="123.96094" y="201.94531" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:32px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve"><tspan>0</tspan></text><text x="124.47363" y="249.4729" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:22px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve"><tspan>m³/hr</tspan></text><g transform="translate(-75,75.001)" style="display: none;" tb:tag="broken">
<g filter="url(#filter0_b_1281_42354)" style="">
<circle cx="200" cy="125" r="111" fill="#000" fill-opacity=".24" style=""/>

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

10
application/src/main/data/json/system/scada_symbols/left-heat-pump.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{
"title": "Left heat pump",
"description": "Left heat pump with configurable connectors, running animation and various states.",
"searchTags": [
@ -26,12 +25,12 @@
},
{
"tag": "fan",
"stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n",
"stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar fanRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!fanRotate) {\n fanRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n fanRotate.speed(speed).play();\n }\n} else {\n if (fanRotate) {\n fanRotate.pause();\n }\n}\n",
"actions": null
},
{
"tag": "fan-blade",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": null
},
{
@ -673,7 +672,8 @@
"step": null
}
]
}]]></tb:metadata><rect x="250" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="250" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356092)" style="fill:url(#paint0_linear_1826_356092)"/><rect x="251.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m303 545h194l32 26h-258z" fill="#fff"/><path d="m303 545h194l32 26h-258z" fill="url(#paint1_linear_1826_356092)" style="fill:url(#paint1_linear_1826_356092)"/><path d="m275.22 569.5 28.308-23h192.93l28.308 23z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-739c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 64h658v72h-658z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h658v72h-658z" fill="url(#paint17_linear_1826_356092)" style="fill:url(#paint17_linear_1826_356092)"/><path d="m15.5 65.5h655v69h-655z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 264h82v72h-82z" fill="#fff" tb:tag="pipe-background"/><path d="m14 264h82v72h-82z" fill="url(#paint25_linear_1826_356092)" style="fill:url(#paint25_linear_1826_356092)"/><path d="m15.5 265.5h79v69h-79z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5" tb:tag="background"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356092)">
}]]></tb:metadata>
<rect x="250" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="250" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356092)" style="fill:url(#paint0_linear_1826_356092)"/><rect x="251.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m303 545h194l32 26h-258z" fill="#fff"/><path d="m303 545h194l32 26h-258z" fill="url(#paint1_linear_1826_356092)" style="fill:url(#paint1_linear_1826_356092)"/><path d="m275.22 569.5 28.308-23h192.93l28.308 23z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-739c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 64h658v72h-658z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h658v72h-658z" fill="url(#paint17_linear_1826_356092)" style="fill:url(#paint17_linear_1826_356092)"/><path d="m15.5 65.5h655v69h-655z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 264h82v72h-82z" fill="#fff" tb:tag="pipe-background"/><path d="m14 264h82v72h-82z" fill="url(#paint25_linear_1826_356092)" style="fill:url(#paint25_linear_1826_356092)"/><path d="m15.5 265.5h79v69h-79z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5" tb:tag="background"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356092)">
<path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-161.7c-0.532 0-1.002-0.027-1.529-0.1-13.196-1.836-202.34-29.137-319.77-101.9-151.45-93.843-252.83-258.48-266.44-281.34-1.0718-1.801-1.5648-3.765-1.5648-5.86z" fill="#e5e5e5" tb:tag="background"/>
<path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-161.7c-0.532 0-1.002-0.027-1.529-0.1-13.196-1.836-202.34-29.137-319.77-101.9-151.45-93.843-252.83-258.48-266.44-281.34-1.0718-1.801-1.5648-3.765-1.5648-5.86z" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/>
<path d="m38.5 12c0-5.799 4.701-10.5 10.5-10.5h739c5.799 0 10.5 4.701 10.5 10.5v521c0 5.799-4.701 10.5-10.5 10.5h-161.7c-0.475 0-0.875-0.024-1.322-0.086-6.567-0.913-57.094-8.188-120.64-24.32-63.568-16.138-140.03-41.11-198.55-77.369-151.12-93.639-252.35-258-265.94-280.83-0.922-1.549-1.3538-3.244-1.3538-5.093z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

2
application/src/main/data/json/system/scada_symbols/left-motor-pump.svg

@ -10,7 +10,7 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": null
},
{

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

17
application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Left tee pipe",
"description": "Left tee pipe with configurable left/top/bottom fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n",
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [
{
"tag": "bottom-fluid",
@ -576,7 +575,8 @@
"step": null
}
]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66573)">
}]]></tb:metadata>
<g clip-path="url(#clip0_1245_66573)">
<path d="m64 186v-172h72v172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m64 186v-172h72v172z" fill="url(#paint0_linear_1245_66573)" style="fill:url(#paint0_linear_1245_66442)"/>
<path d="m65.5 184.5v-169h69v169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -756,9 +756,12 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
<clipPath id="clip0_1245_66573">
<rect width="200" height="200" fill="#fff"/>
</clipPath>

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

48
application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Left top elbow pipe",
"description": "Left top elbow pipe with fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(delta, -delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n",
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [
{
"tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null
}
]
}]]></tb:metadata><g transform="rotate(90,100,100)" clip-path="url(#clip0_1245_66459)">
}]]></tb:metadata>
<g transform="rotate(90,100,100)" clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
@ -530,7 +499,12 @@
<clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/>
<pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<pattern id="base-vertical-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(-90)" x="-64" y="64" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m64 64v-0.1h72c-0.0899 0.87013-0.23101 2.2585-0.44644 3.7063-0.48479 3.257-1.345 7.819-2.8448 13.02-3.0056 10.422-8.5473 23.299-18.676 33.421-10.129 10.122-23.014 15.66-33.444 18.663-5.2052 1.4993-9.7699 2.3586-13.03 2.843-1.4481 0.21507-2.789 0.35602-3.6593 0.44685v-72z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(.99929 0 0 .99919 .089337 .10125)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" style="" tb:tag="center-fluid-background"/>
<path d="m132.05 63.65s-0.0216 31.924-17.622 50.777c-17.142 17.939-50.672 17.628-50.672 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

11
application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Long horizontal pipe",
"description": "Long horizontal pipe with fluid and leak visualizations.",
"searchTags": [
@ -11,7 +10,7 @@
"tags": [
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -240,7 +239,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m14 64h372v72h-372v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h372v72h-372v-72z" fill="url(#paint0_linear_1239_51060)"/><g stroke-width="3">
}]]></tb:metadata>
<path d="m14 64h372v72h-372v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h372v72h-372v-72z" fill="url(#paint0_linear_1239_51060)"/><g stroke-width="3">
<path d="m15.5 65.5h369v69h-369v-69z" stroke="#000" stroke-opacity=".12"/>
<rect x="387.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
<rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

11
application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{
"title": "Long vertical pipe",
"description": "Long vertical pipe with fluid and leak visualizations.",
"searchTags": [
@ -11,7 +10,7 @@
"tags": [
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -240,7 +239,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m64 386v-372h72v372h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 386v-372h72v372h-72z" fill="url(#paint0_linear_1242_51682)"/><g stroke-width="3">
}]]></tb:metadata>
<path d="m64 386v-372h72v372h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 386v-372h72v372h-72z" fill="url(#paint0_linear_1242_51682)"/><g stroke-width="3">
<path d="m65.5 384.5v-369h69v369h-69z" stroke="#000" stroke-opacity=".12"/>
<rect transform="rotate(-90 51.5 398.5)" x="51.5" y="398.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
<rect transform="rotate(-90 51.5 12.5)" x="51.5" y="12.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

13
application/src/main/data/json/system/scada_symbols/pool.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2400" height="800" fill="none" version="1.1" viewBox="0 0 2400 800">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2400" height="800" fill="none" version="1.1" viewBox="0 0 2400 800"><tb:metadata xmlns=""><![CDATA[{
"title": "Pool",
"description": "Pool with current volume value and level visualizations.",
"searchTags": [
@ -24,12 +23,12 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
@ -279,7 +278,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="#E5E5E5" tb:tag="background"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="url(#paint0_linear_2028_445065)"/><path d="m1.5 12c0-5.799 4.701-10.5 10.5-10.5h2376c5.8 0 10.5 4.701 10.5 10.5v776c0 5.799-4.7 10.5-10.5 10.5h-2376c-5.799 0-10.5-4.701-10.5-10.5v-776z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.628-5.37 12-12 12h-2376c-6.6274 0-12-5.372-12-12v-776z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_2028_445065" x="16" y="56" width="2368" height="728" style="mask-type:alpha" maskUnits="userSpaceOnUse">
}]]></tb:metadata>
<path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="#E5E5E5" tb:tag="background"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="url(#paint0_linear_2028_445065)"/><path d="m1.5 12c0-5.799 4.701-10.5 10.5-10.5h2376c5.8 0 10.5 4.701 10.5 10.5v776c0 5.799-4.7 10.5-10.5 10.5h-2376c-5.799 0-10.5-4.701-10.5-10.5v-776z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.628-5.37 12-12 12h-2376c-6.6274 0-12-5.372-12-12v-776z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_2028_445065" x="16" y="56" width="2368" height="728" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m16 56h359 355 444.5 394 230 238.5 343.13c2.21 0 3.87 1.7909 3.87 4v712c0 6.627-5.37 12-12 12h-2344c-6.6274 0-12-5.373-12-12v-716z" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_2028_445065)">
<rect transform="scale(1,-1)" x="16" y="-790" width="2368" height="600" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -324,7 +324,7 @@
<stop stop-color="#727171" stop-opacity=".7" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient13024" x1="2400" x2="6.4263" y1="402.68" y2="276.76" gradientUnits="userSpaceOnUse" xlink:href="#paint0_linear_2028_445065"/>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -472,6 +472,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

4
application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg

@ -19,7 +19,7 @@
},
{
"tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -47,7 +47,7 @@
},
{
"tag": "pointer",
"stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.animate(element, 1000).ease('-').transform({rotate: degrees});\n}",
"stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.cssAnimate(element, 500).ease('ease-in').transform({rotate: degrees});\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

13
application/src/main/data/json/system/scada_symbols/right-flow-meter.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

10
application/src/main/data/json/system/scada_symbols/right-heat-pump.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{
"title": "Right heat pump",
"description": "Right heat pump with configurable connectors, running animation and various states.",
"searchTags": [
@ -26,12 +25,12 @@
},
{
"tag": "fan",
"stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n",
"stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar fanRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!fanRotate) {\n fanRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n fanRotate.speed(speed).play();\n }\n} else {\n if (fanRotate) {\n fanRotate.pause();\n }\n}",
"actions": null
},
{
"tag": "fan-blade",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": null
},
{
@ -673,7 +672,8 @@
"step": null
}
]
}]]></tb:metadata><rect x="254" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="254" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356190)"/><rect x="255.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m307 545h194l32 26h-258l32-26z" fill="#fff"/><path d="m307 545h194l32 26h-258l32-26z" fill="url(#paint1_linear_1826_356190)"/><path d="m279.22 569.5 28.308-23h192.93l28.308 23h-249.55z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="#E5E5E5"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356190)"/><rect transform="matrix(-1 0 0 1 764 0)" x="-1.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h739c6.627 0 12-5.373 12-12v-521z" fill="#4A4848" fill-opacity=".5"/><rect transform="matrix(-1 0 0 1 801 50)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 64h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 64h-23v72h23v-72z" fill="url(#paint3_linear_1826_356190)"/><path d="m788.5 65.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 801 250)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 264h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 264h-23v72h23v-72z" fill="url(#paint4_linear_1826_356190)"/><path d="m788.5 265.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="4" width="763" height="545" rx="12" fill="#E5E5E5" tb:tag="background"/><rect x="4" width="763" height="545" rx="12" fill="url(#paint5_linear_1826_356190)"/><rect x="5.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356190)">
}]]></tb:metadata>
<rect x="254" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="254" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356190)"/><rect x="255.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m307 545h194l32 26h-258l32-26z" fill="#fff"/><path d="m307 545h194l32 26h-258l32-26z" fill="url(#paint1_linear_1826_356190)"/><path d="m279.22 569.5 28.308-23h192.93l28.308 23h-249.55z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="#E5E5E5"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356190)"/><rect transform="matrix(-1 0 0 1 764 0)" x="-1.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h739c6.627 0 12-5.373 12-12v-521z" fill="#4A4848" fill-opacity=".5"/><rect transform="matrix(-1 0 0 1 801 50)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 64h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 64h-23v72h23v-72z" fill="url(#paint3_linear_1826_356190)"/><path d="m788.5 65.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 801 250)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 264h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 264h-23v72h23v-72z" fill="url(#paint4_linear_1826_356190)"/><path d="m788.5 265.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="4" width="763" height="545" rx="12" fill="#E5E5E5" tb:tag="background"/><rect x="4" width="763" height="545" rx="12" fill="url(#paint5_linear_1826_356190)"/><rect x="5.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356190)">
<path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h161.7c0.532 0 1.002-0.027 1.529-0.1 13.196-1.836 202.34-29.137 319.77-101.9 151.45-93.843 252.83-258.48 266.44-281.34 1.072-1.801 1.565-3.765 1.565-5.86v-143.8z" fill="#E5E5E5" tb:tag="background"/>
<path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h161.7c0.532 0 1.002-0.027 1.529-0.1 13.196-1.836 202.34-29.137 319.77-101.9 151.45-93.843 252.83-258.48 266.44-281.34 1.072-1.801 1.565-3.765 1.565-5.86v-143.8z" fill="url(#paint6_linear_1826_356190)"/>
<path d="m765.5 12c0-5.799-4.701-10.5-10.5-10.5h-739c-5.799 0-10.5 4.701-10.5 10.5v521c0 5.799 4.701 10.5 10.5 10.5h161.7c0.475 0 0.875-0.024 1.322-0.086 6.567-0.913 57.094-8.188 120.64-24.32 63.568-16.138 140.03-41.11 198.55-77.369 151.12-93.639 252.34-258 265.94-280.83 0.922-1.549 1.354-3.244 1.354-5.093v-143.8z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

2
application/src/main/data/json/system/scada_symbols/right-motor-pump.svg

@ -10,7 +10,7 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": null
},
{

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

17
application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Right tee pipe",
"description": "Right tee pipe with configurable right/top/bottom fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n",
"stateRenderFunction": "var rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [
{
"tag": "bottom-fluid",
@ -576,7 +575,8 @@
"step": null
}
]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66636)">
}]]></tb:metadata>
<g clip-path="url(#clip0_1245_66636)">
<path d="m64 186v-172h72v172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m64 186v-172h72v172z" fill="url(#paint0_linear_1245_66636)" style="fill:url(#paint0_linear_1245_66442)"/>
<path d="m65.5 184.5v-169h69v169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -756,9 +756,12 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
<clipPath id="clip0_1245_66636">
<rect width="200" height="200" fill="#fff"/>
</clipPath>

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

2
application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg

@ -10,7 +10,7 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": null
},
{

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

8
application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

15
application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="600" fill="none" version="1.1" viewBox="0 0 600 600">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="600" fill="none" version="1.1" viewBox="0 0 600 600"><tb:metadata xmlns=""><![CDATA[{
"title": "Small spherical tank",
"description": "Small spherical tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 560 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(268, y, 300, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 258, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(280, minorY, 300, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 560 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(268, y, 300, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 258, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(280, minorY, 300, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -556,7 +555,8 @@
"step": null
}
]
}]]></tb:metadata><circle cx="300" cy="300" r="300" fill="#E5E5E5" tb:tag="background"/><circle cx="300" cy="300" r="300" fill="url(#paint0_radial_1711_268272)"/><circle cx="300" cy="300" r="298.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="300" cy="300" r="300" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_268272" x="16" y="16" width="568" height="568" style="mask-type:alpha" maskUnits="userSpaceOnUse">
}]]></tb:metadata>
<circle cx="300" cy="300" r="300" fill="#E5E5E5" tb:tag="background"/><circle cx="300" cy="300" r="300" fill="url(#paint0_radial_1711_268272)"/><circle cx="300" cy="300" r="298.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="300" cy="300" r="300" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_268272" x="16" y="16" width="568" height="568" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<circle cx="300" cy="300" r="284" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1711_268272)">
<rect transform="scale(1,-1)" y="-584" width="600" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -694,7 +694,7 @@
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_1694_158298" result="effect2_innerShadow_1694_158298"/>
</filter>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -842,6 +842,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

15
application/src/main/data/json/system/scada_symbols/spherical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{
"title": "Spherical tank",
"description": "Spherical tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 960 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(458, y, 490, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 448, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(470, minorY, 490, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 960 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(458, y, 490, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 448, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(470, minorY, 490, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -556,7 +555,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="#E5E5E5" tb:tag="background"/><path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="url(#paint0_radial_1711_251491)"/><path d="m998.5 500c0 275.31-223.19 498.5-498.5 498.5s-498.5-223.19-498.5-498.5 223.19-498.5 498.5-498.5 498.5 223.19 498.5 498.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="500" cy="500" r="500" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_251491" x="16" y="16" width="968" height="968" style="mask-type:alpha" maskUnits="userSpaceOnUse">
}]]></tb:metadata>
<path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="#E5E5E5" tb:tag="background"/><path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="url(#paint0_radial_1711_251491)"/><path d="m998.5 500c0 275.31-223.19 498.5-498.5 498.5s-498.5-223.19-498.5-498.5 223.19-498.5 498.5-498.5 498.5 223.19 498.5 498.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="500" cy="500" r="500" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_251491" x="16" y="16" width="968" height="968" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<circle cx="500" cy="500" r="484" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1711_251491)">
<rect transform="scale(1,-1)" x="8" y="-984" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -724,7 +724,7 @@
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_1694_158298" result="effect2_innerShadow_1694_158298"/>
</filter>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -872,6 +872,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

15
application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"><tb:metadata xmlns=""><![CDATA[{
"title": "Stand cylindrical tank",
"description": "Stand cylindrical tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -561,7 +560,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white">
}]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -654,7 +654,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -802,6 +802,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 120 KiB

15
application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="800" fill="none" version="1.1" viewBox="0 0 1e3 800">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="800" fill="none" version="1.1" viewBox="0 0 1e3 800"><tb:metadata xmlns=""><![CDATA[{
"title": "Stand horizontal tank",
"description": "Stand horizontal tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -556,7 +555,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse">
}]]></tb:metadata>
<path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m178.68 16h654.82c117.67 0 151.5 98.586 151.5 184.16v204.18c0 135.62-68.502 181.66-162.5 181.66h-627.98c-129 0-177.5-66.558-177.5-181.66s0.0023-129.32 0-212.19c-3e-4 -9.509-5.4994-176.16 161.66-176.16z" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1694_158298)">
<rect transform="scale(1,-1)" x="9" y="-585" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -676,7 +676,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -824,6 +824,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

15
application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="800" fill="none" version="1.1" viewBox="0 0 800 800">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="800" fill="none" version="1.1" viewBox="0 0 800 800"><tb:metadata xmlns=""><![CDATA[{
"title": "Stand vertical short tank",
"description": "Stand vertical short tank with current volume value and level visualizations.",
"searchTags": [
@ -26,17 +25,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 245 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 245 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -562,7 +561,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white">
}]]></tb:metadata>
<path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -643,7 +643,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -791,6 +791,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

15
application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"><tb:metadata xmlns=""><![CDATA[{
"title": "Stand vertical tank",
"description": "Stand vertical tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -561,7 +560,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff">
}]]></tb:metadata>
<path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill="#e5e5e5" fill-rule="evenodd" tb:tag="background"/>
@ -1243,7 +1243,7 @@
<stop stop-color="#fff" stop-opacity=".25" offset="0"/>
<stop stop-opacity=".1" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -1391,6 +1391,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint147_linear_1687_130893" x1="59.818" x2="80.389" y1="1175" y2="1208.9" gradientUnits="userSpaceOnUse">
<stop stop-color="#727171" stop-opacity=".2" offset="0"/>
<stop stop-color="#727171" offset="1"/>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

13
application/src/main/data/json/system/scada_symbols/top-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
"title": "Top flow meter",
"description": "Top flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [
@ -20,7 +19,7 @@
},
{
"tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
},
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -733,7 +732,8 @@
"step": null
}
]
}]]></tb:metadata><rect x="14" y="264" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="264" width="372" height="72" fill="url(#paint0_linear_1253_89545)"/><rect x="15.5" y="265.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="14" y="264" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="264" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="#fff" tb:tag="pipe-background"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="url(#paint1_linear_1253_89545)"/><path d="m234.5 237.5v26.242l-34.5 30.263-34.5-30.263v-26.242h69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><mask id="path-7-inside-1_1253_89545" fill="white">
}]]></tb:metadata>
<rect x="14" y="264" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="264" width="372" height="72" fill="url(#paint0_linear_1253_89545)"/><rect x="15.5" y="265.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="14" y="264" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="264" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="#fff" tb:tag="pipe-background"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="url(#paint1_linear_1253_89545)"/><path d="m234.5 237.5v26.242l-34.5 30.263-34.5-30.263v-26.242h69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><mask id="path-7-inside-1_1253_89545" fill="white">
<path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/>
</mask><path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4A4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="200" cy="125" r="111" fill="#fff" tb:tag="background"/><rect x="119.5" y="105.5" width="161" height="39" rx="3.8571" fill="#4A4848" fill-opacity=".06" tb:tag="border"/><path d="m123.36 104c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4A4848" tb:tag="border"/><circle cx="166" cy="73" r="16" fill="url(#paint2_linear_1253_89545)"/><circle cx="166" cy="73" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="234" cy="73" r="16" fill="url(#paint3_linear_1253_89545)"/><circle cx="234" cy="73" r="15.5" stroke="#000" stroke-opacity=".12"/><defs>
<linearGradient id="paint0_linear_1253_89545" x1="110.72" x2="110.63" y1="264" y2="336.01" gradientUnits="userSpaceOnUse">
@ -768,7 +768,7 @@
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/>
<feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/>
</filter>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-24)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-24)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -799,6 +799,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

50
application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Top right elbow pipe",
"description": "Top right elbow pipe with fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(delta, -delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n",
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [
{
"tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null
}
]
}]]></tb:metadata><g transform="rotate(180,100,100)" clip-path="url(#clip0_1245_66459)">
}]]></tb:metadata>
<g transform="rotate(180,100,100)" clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
@ -530,9 +499,14 @@
<clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/>
</clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/>
<pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<pattern id="base-vertical-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="136" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m136 63.85 0.1 72.05c-0.87135-0.09-2.1615-0.23117-3.6113-0.44675-3.2616-0.48513-7.8299-1.3459-13.038-2.8468-10.437-3.0077-23.332-8.5533-33.468-18.689-10.136-10.136-15.682-23.03-18.689-33.467-1.5014-5.2088-2.3619-9.7767-2.847-13.039-0.21538-1.4491-0.35652-2.6909-0.44748-3.5618z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(0 .99929 -.99919 0 199.9 .095299)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" tb:tag="center-fluid-background" style=""/>
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" style="" tb:tag="center-fluid-background"/>
<path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.814 17.618-50.678 17.628-50.678 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>
<path d="m63.95 63.954v-0.17285l7.9056-0.02731v8.1066h-8.2058v-7.9066z" style="fill: rgb(114, 113, 113); stroke-width: 1.007;"/>
</g><g transform="rotate(180 100 100)" style="display: none;" tb:tag="leak">

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

17
application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Top tee pipe",
"description": "Top tee pipe with configurable left/right/top fluid and leak visualizations.",
"searchTags": [
@ -8,7 +7,7 @@
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n",
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [
{
"tag": "leak",
@ -576,7 +575,8 @@
"step": null
}
]
}]]></tb:metadata><g transform="translate(-4)" clip-path="url(#clip0_1245_66653)">
}]]></tb:metadata>
<g transform="translate(-4)" clip-path="url(#clip0_1245_66653)">
<path d="m18 64h172v72h-172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m18 64h172v72h-172z" fill="url(#paint0_linear_1245_66653)" style="fill:url(#paint0_linear_1245_66653)"/>
<path d="m19.5 65.5h169v69h-169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -738,9 +738,12 @@
<stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<clipPath id="clip0_1245_66653">
<rect transform="translate(4)" width="200" height="200" fill="#fff"/>
</clipPath>

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

10
application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Vertical ball valve",
"description": "Vertical ball valve with open/close animation and state colors.",
"searchTags": [
@ -11,7 +10,7 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null
},
{
@ -25,7 +24,7 @@
},
{
"tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originY: 100});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle, originY: 100});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originY: 100});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle, originY: 100});\n element.remember('openAnimate', false);\n}\n",
"actions": null
}
],
@ -200,7 +199,8 @@
"step": null
}
]
}]]></tb:metadata><defs>
}]]></tb:metadata>
<defs>
<linearGradient id="paint0_linear_1595_99338" x1="58.72" x2="58.3" y1="46.5" y2="153.51" gradientTransform="rotate(-90,100,100)" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".18316"/>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

13
application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{
"title": "Vertical inline flow meter",
"description": "Vertical inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [
@ -20,7 +19,7 @@
},
{
"tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
},
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -733,7 +732,8 @@
"step": null
}
]
}]]></tb:metadata><rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="url(#paint0_linear_1595_101503)" style="fill:url(#paint0_linear_1595_101503)"/><rect transform="rotate(-90)" x="-384.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(-90)" x="-12.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(-90)" x="-398.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><defs>
}]]></tb:metadata>
<rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="url(#paint0_linear_1595_101503)" style="fill:url(#paint0_linear_1595_101503)"/><rect transform="rotate(-90)" x="-384.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(-90)" x="-12.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(-90)" x="-398.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><defs>
<mask id="path-5-inside-1_1595_102361" fill="#ffffff">
<path d="m300 100c0 55.228-44.772 100-100 100s-100-44.772-100-100 44.772-100 100-100 100 44.772 100 100zm-188 0c0 48.603 39.401 88.004 88.004 88.004s88.004-39.401 88.004-88.004c0-48.604-39.401-88.004-88.004-88.004s-88.004 39.401-88.004 88.004z"/>
</mask>
@ -754,7 +754,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -785,6 +785,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

11
application/src/main/data/json/system/scada_symbols/vertical-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Vertical pipe",
"description": "Vertical pipe with fluid and leak visualizations.",
"searchTags": [
@ -11,7 +10,7 @@
"tags": [
{
"tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null
},
{
@ -240,7 +239,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m64 186v-172h72v172h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 186v-172h72v172h-72z" fill="url(#paint0_linear_1245_55957)"/><g stroke-width="3">
}]]></tb:metadata>
<path d="m64 186v-172h72v172h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 186v-172h72v172h-72z" fill="url(#paint0_linear_1245_55957)"/><g stroke-width="3">
<path d="m65.5 184.5v-169h69v169h-69z" stroke="#000" stroke-opacity=".12"/>
<rect transform="rotate(-90 51.5 198.5)" x="51.5" y="198.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
<rect transform="rotate(-90 51.5 12.5)" x="51.5" y="12.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

15
application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{
"title": "Vertical short tank",
"description": "Vertical short tank with current volume value and level visualizations.",
"searchTags": [
@ -25,17 +24,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 245 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 245 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -561,7 +560,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white">
}]]></tb:metadata>
<path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -642,7 +642,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -790,6 +790,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

15
application/src/main/data/json/system/scada_symbols/vertical-tank.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"><tb:metadata xmlns=""><![CDATA[{
"title": "Vertical tank",
"description": "Vertical tank with current volume value and level visualizations.",
"searchTags": [
@ -24,17 +23,17 @@
},
{
"tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null
},
{
"tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -560,7 +559,8 @@
"step": null
}
]
}]]></tb:metadata><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff">
}]]></tb:metadata>
<path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill="#e5e5e5" fill-rule="evenodd" tb:tag="background"/>
@ -1242,7 +1242,7 @@
<stop stop-color="#fff" stop-opacity=".25" offset="0"/>
<stop stop-opacity=".1" offset="1"/>
</linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -1390,5 +1390,6 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
</defs><path d="m201.79-5e-4s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/>
</svg>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

4
application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg

@ -10,7 +10,7 @@
"tags": [
{
"tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null
},
{
@ -24,7 +24,7 @@
},
{
"tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n",
"actions": null
}
],

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

12
application/src/main/data/json/system/scada_symbols/waterstop.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Water stop",
"description": "Remotely controlled water shutoff valve with configurable connectors and various states.",
"searchTags": [
@ -25,7 +24,7 @@
},
{
"tag": "walve",
"stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}\n",
"stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetCssAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": null
}
],
@ -93,7 +92,7 @@
},
"valueToData": {
"type": "CONSTANT",
"constantValue": false,
"constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
@ -127,7 +126,7 @@
},
"valueToData": {
"type": "CONSTANT",
"constantValue": true,
"constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
@ -219,7 +218,8 @@
"step": null
}
]
}]]></tb:metadata><path d="M14 64H186V136H14V64Z" fill="#fff" tb:tag="pipe-color"/><path d="M14 64H186V136H14V64Z" fill="url(#paint0_linear_2089_217683)"/><path d="m15.5 65.5h169v69h-169v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="187.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="#1C943E" tb:tag="walve"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="url(#paint1_linear_2089_217683)"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" stroke="#fff"/><path d="m110 136v-72h-21v72h21z" fill="#1C943E" tb:tag="walve"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint2_linear_2089_217683)"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint3_linear_2089_217683)"/><path d="m109.5 135.5v-71h-20v71h20z" stroke="#000" stroke-opacity=".12"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="#647484"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="url(#paint4_linear_2089_217683)"/><path d="m126 62.5c1.381 0 2.5-1.1193 2.5-2.5v-6c0-1.3807-1.119-2.5-2.5-2.5h-52c-1.3807 0-2.5 1.1193-2.5 2.5v6c0 1.3807 1.1193 2.5 2.5 2.5h52z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m67.262 0s-67.262 0-67.262 33.5v164.18c0 1.3256 1.7909 2.3202 4 2.3202h192c2.209 0 4-0.9946 4-2.3202v-164.18c0-33.5-66.07-33.5-66.07-33.5h-33.93zm67.07 40.6c-1.2887 0-2.3332 0.6268-2.3332 1.4v150.2c0 0.7732 1.0447 1.4 2.3332 1.4h14.667c1.2887 0 2.3332-0.6268 2.3332-1.4v-150.2c0-0.7732-1.0447-1.4-2.3332-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
}]]></tb:metadata>
<path d="M14 64H186V136H14V64Z" fill="#fff" tb:tag="pipe-color"/><path d="M14 64H186V136H14V64Z" fill="url(#paint0_linear_2089_217683)"/><path d="m15.5 65.5h169v69h-169v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="187.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="#1C943E" tb:tag="walve"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="url(#paint1_linear_2089_217683)"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" stroke="#fff"/><path d="m110 136v-72h-21v72h21z" fill="#1C943E" tb:tag="walve"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint2_linear_2089_217683)"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint3_linear_2089_217683)"/><path d="m109.5 135.5v-71h-20v71h20z" stroke="#000" stroke-opacity=".12"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="#647484"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="url(#paint4_linear_2089_217683)"/><path d="m126 62.5c1.381 0 2.5-1.1193 2.5-2.5v-6c0-1.3807-1.119-2.5-2.5-2.5h-52c-1.3807 0-2.5 1.1193-2.5 2.5v6c0 1.3807 1.1193 2.5 2.5 2.5h52z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m67.262 0s-67.262 0-67.262 33.5v164.18c0 1.3256 1.7909 2.3202 4 2.3202h192c2.209 0 4-0.9946 4-2.3202v-164.18c0-33.5-66.07-33.5-66.07-33.5h-33.93zm67.07 40.6c-1.2887 0-2.3332 0.6268-2.3332 1.4v150.2c0 0.7732 1.0447 1.4 2.3332 1.4h14.667c1.2887 0 2.3332-0.6268 2.3332-1.4v-150.2c0-0.7732-1.0447-1.4-2.3332-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217683" x1="58.72" x2="58.53" y1="64" y2="136" gradientUnits="userSpaceOnUse">
<stop stop-color="#727171" offset="0"/>
<stop stop-color="#727171" stop-opacity=".35" offset=".26388"/>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

2
application/src/main/data/json/system/widget_types/scada_symbol.json

File diff suppressed because one or more lines are too long

4
pom.xml

@ -69,8 +69,8 @@
<apache-httpclient.version>4.5.14</apache-httpclient.version>
<apache-httpcore.version>4.4.16</apache-httpcore.version>
<joda-time.version>2.12.7</joda-time.version>
<jackson.version>2.17.0</jackson.version>
<jackson-databind.version>2.17.0</jackson-databind.version>
<jackson.version>2.17.2</jackson.version>
<jackson-databind.version>2.17.2</jackson-databind.version>
<fasterxml-classmate.version>1.7.0</fasterxml-classmate.version>
<auth0-jwt.version>4.4.0</auth0-jwt.version>
<json-schema-validator.version>2.2.14</json-schema-validator.version>

49
ui-ngx/patches/angular-gridster2+15.0.4.patch

@ -1,44 +1,15 @@
diff --git a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
index cf4e220..4275d11 100644
index cf4e220..df51c91 100644
--- a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
+++ b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
@@ -208,6 +208,7 @@ const GridsterConfigService = {
useTransformPositioning: true,
scrollSensitivity: 10,
scrollSpeed: 20,
+ colWidthUpdateCallback: undefined,
initCallback: undefined,
destroyCallback: undefined,
gridSizeChangedCallback: undefined,
@@ -1243,6 +1244,9 @@ class GridsterComponent {
this.renderer.setStyle(this.el, 'padding-right', this.$options.margin + 'px');
}
this.curColWidth = (this.curWidth - marginWidth) / this.columns;
+ if (this.options.colWidthUpdateCallback) {
+ this.curColWidth = this.options.colWidthUpdateCallback(this.curColWidth);
+ }
let marginHeight = -this.$options.margin;
if (this.$options.outerMarginTop !== null) {
marginHeight += this.$options.outerMarginTop;
@@ -1266,6 +1270,9 @@ class GridsterComponent {
@@ -666,8 +666,8 @@ class GridsterRenderer {
renderer.setStyle(el, DirTypes.LTR ? 'margin-right' : 'margin-left', '');
}
else {
this.curColWidth = (this.curWidth + this.$options.margin) / this.columns;
+ if (this.options.colWidthUpdateCallback) {
+ this.curColWidth = this.options.colWidthUpdateCallback(this.curColWidth);
+ }
this.curRowHeight =
((this.curHeight + this.$options.margin) / this.rows) *
this.$options.rowHeightRatio;
diff --git a/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts b/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts
index 1d7cdf0..a712b35 100644
--- a/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts
+++ b/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts
@@ -73,6 +73,7 @@ export interface GridsterConfig {
useTransformPositioning?: boolean;
scrollSensitivity?: number | null;
scrollSpeed?: number;
+ colWidthUpdateCallback?: (colWidth: number) => number;
initCallback?: (gridster: GridsterComponentInterface) => void;
destroyCallback?: (gridster: GridsterComponentInterface) => void;
gridSizeChangedCallback?: (gridster: GridsterComponentInterface) => void;
- const x = Math.round(this.gridster.curColWidth * item.x);
- const y = Math.round(this.gridster.curRowHeight * item.y);
+ const x = this.gridster.curColWidth * item.x;
+ const y = this.gridster.curRowHeight * item.y;
const width = this.gridster.curColWidth * item.cols - this.gridster.$options.margin;
const height = this.gridster.curRowHeight * item.rows - this.gridster.$options.margin;
// set the cell style

1
ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html

@ -43,7 +43,6 @@
[widgetLayouts]="layoutCtx.widgetLayouts"
[columns]="columns"
[displayGrid]="displayGrid"
[colWidthInteger]="colWidthInteger"
[outerMargin]="outerMargin"
[margin]="margin"
[aliasController]="dashboardCtx.aliasController"

4
ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts

@ -109,10 +109,6 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
return this.layoutCtx.gridSettings.mobileRowHeight;
}
get colWidthInteger(): boolean {
return this.isScada;
}
get columns(): number {
return this.layoutCtx.gridSettings.minColumns || this.layoutCtx.gridSettings.columns || 24;
}

11
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts

@ -87,10 +87,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
@Input()
columns: number;
@Input()
@coerceBoolean()
colWidthInteger = false;
@Input()
@coerceBoolean()
setGridSize = false;
@ -260,13 +256,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
itemChangeCallback: () => this.dashboardWidgets.sortWidgets(),
itemInitCallback: (_, itemComponent) => {
(itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent;
},
colWidthUpdateCallback: (colWidth) => {
if (this.colWidthInteger) {
return Math.floor(colWidth);
} else {
return colWidth;
}
}
};

6
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts

@ -530,8 +530,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
this.removeObserveAttrTelemetryFromJson(ATTRIBUTE, value.keyId);
this.removeKeyNameFromJson(value.keyId);
this.removeAttributesFromJson(value.keyId);
this.updateObserveAttrTelemetryObjectFormGroup(objectsOld);
}
this.lwm2mDeviceProfileFormGroup.patchValue({
observeAttrTelemetry: deepClone(objectsOld)
}, {emitEvent: false});
};
private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, keyId: string): void => {
const isIdIndex = (element) => element.startsWith(`/${keyId}`);

8
ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts

@ -16,10 +16,12 @@
import {
GatewayConnector,
LegacySlaveConfig,
ModbusBasicConfig,
ModbusBasicConfig_v3_5_2,
ModbusLegacyBasicConfig,
ModbusLegacySlave,
ModbusMasterConfig,
ModbusSlave,
} from '../gateway-widget.models';
import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract';
@ -40,7 +42,7 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor<any
...this.connector,
configurationJson: {
master: configurationJson.master?.slaves
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master)
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master as ModbusMasterConfig<LegacySlaveConfig>)
: { slaves: [] },
slave: configurationJson.slave
? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave)
@ -59,7 +61,9 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor<any
slave: configurationJson.slave
? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(configurationJson.slave as ModbusSlave)
: {} as ModbusLegacySlave,
master: configurationJson.master,
master: configurationJson.master?.slaves
? ModbusVersionMappingUtil.mapMasterToDowngradedVersion(configurationJson.master as ModbusMasterConfig)
: { slaves: [] },
},
configVersion: this.gatewayVersionIn
} as GatewayConnector<ModbusLegacyBasicConfig>;

6
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts

@ -25,8 +25,8 @@ import {
} from '@home/components/widget/lib/gateway/gateway-widget.models';
@Directive()
export abstract class ModbusBasicConfigDirective<BasicConfig>
extends GatewayConnectorBasicConfigDirective<ModbusBasicConfig_v3_5_2, BasicConfig> {
export abstract class ModbusBasicConfigDirective<InputBasicConfig, OutputBasicConfig>
extends GatewayConnectorBasicConfigDirective<InputBasicConfig, OutputBasicConfig> {
enableSlaveControl: FormControl<boolean> = new FormControl(false);
@ -41,7 +41,7 @@ export abstract class ModbusBasicConfigDirective<BasicConfig>
});
}
override writeValue(basicConfig: BasicConfig & ModbusBasicConfig): void {
override writeValue(basicConfig: OutputBasicConfig & ModbusBasicConfig): void {
super.writeValue(basicConfig);
this.onEnableSlaveControl(basicConfig);
}

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html

@ -20,7 +20,7 @@
<ng-container [ngTemplateOutlet]="generalTabContent"></ng-container>
</mat-tab>
<mat-tab label="{{ 'gateway.master-connections' | translate }}">
<tb-modbus-master-table formControlName="master"></tb-modbus-master-table>
<tb-modbus-master-table [isLegacy]="isLegacy" formControlName="master"></tb-modbus-master-table>
</mat-tab>
<mat-tab label="{{ 'gateway.server-config' | translate }}">
<div class="tb-form-panel no-border no-padding padding-top">

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts

@ -56,7 +56,9 @@ import {
],
styleUrls: ['./modbus-basic-config.component.scss'],
})
export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig_v3_5_2> {
export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig_v3_5_2, ModbusBasicConfig_v3_5_2> {
isLegacy = false;
protected override mapConfigToFormValue({ master, slave }: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 {
return {

26
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts

@ -17,8 +17,10 @@
import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
ModbusBasicConfig_v3_5_2,
ModbusLegacyBasicConfig, ModbusLegacySlave,
LegacySlaveConfig,
ModbusBasicConfig,
ModbusLegacyBasicConfig,
ModbusLegacySlave,
ModbusMasterConfig,
ModbusSlave
} from '@home/components/widget/lib/gateway/gateway-widget.models';
@ -58,21 +60,21 @@ import {
],
styleUrls: ['./modbus-basic-config.component.scss'],
})
export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective<ModbusLegacyBasicConfig> {
export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig, ModbusLegacyBasicConfig> {
protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 {
isLegacy = true;
protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig {
return {
master: config.master?.slaves
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master)
: { slaves: [] } as ModbusMasterConfig,
slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave,
};
master: config.master?.slaves ? config.master : { slaves: [] } as ModbusMasterConfig<LegacySlaveConfig>,
slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave as ModbusLegacySlave) : {},
} as ModbusBasicConfig;
}
protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusLegacyBasicConfig {
protected override getMappedValue(value: ModbusBasicConfig): ModbusLegacyBasicConfig {
return {
master: value.master,
slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave) : {} as ModbusLegacySlave,
master: value.master as ModbusMasterConfig<LegacySlaveConfig>,
slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave as ModbusSlave) : {} as ModbusLegacySlave,
};
}
}

5
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html

@ -221,6 +221,11 @@
</mat-form-field>
</div>
</div>
<tb-report-strategy
*ngIf="withReportStrategy"
formControlName="reportStrategy"
[isExpansionMode]="true"
/>
</div>
</ng-template>
</mat-expansion-panel>

37
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts

@ -28,7 +28,7 @@ import {
import { TbPopoverComponent } from '@shared/components/popover.component';
import {
ModbusDataType,
ModbusEditableDataTypes,
ModbusEditableDataTypes, ModbusFormValue,
ModbusFunctionCodeTranslationsMap,
ModbusObjectCountByDataType,
ModbusValue,
@ -45,6 +45,9 @@ import { generateSecret } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import {
ReportStrategyComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
@Component({
selector: 'tb-modbus-data-keys-panel',
@ -55,12 +58,15 @@ import { Subject } from 'rxjs';
CommonModule,
SharedModule,
GatewayHelpLinkPipe,
ReportStrategyComponent,
]
})
export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
@coerceBoolean()
@Input() isMaster = false;
@coerceBoolean()
@Input() hideNewFields = false;
@Input() panelTitle: string;
@Input() addKeyTitle: string;
@Input() deleteKeyTitle: string;
@ -75,6 +81,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
modbusDataTypes = Object.values(ModbusDataType);
modifierTypes: ModifierType[] = Object.values(ModifierType);
withFunctionCode = true;
withReportStrategy = true;
enableModifiersControlMap = new Map<string, FormControl<boolean>>();
showModifiersMap = new Map<string, boolean>();
@ -96,6 +103,9 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.withFunctionCode = !this.isMaster || (this.keysType !== ModbusValueKey.ATTRIBUTES && this.keysType !== ModbusValueKey.TIMESERIES);
this.withReportStrategy = !this.isMaster
&& (this.keysType === ModbusValueKey.ATTRIBUTES || this.keysType === ModbusValueKey.TIMESERIES)
&& !this.hideNewFields;
this.keysListFormArray = this.prepareKeysFormArray(this.values);
this.defaultFunctionCodes = this.getDefaultFunctionCodes();
}
@ -118,6 +128,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
address: [null, [Validators.required]],
objectsCount: [1, [Validators.required]],
functionCode: [{ value: this.getDefaultFunctionCodes()[0], disabled: !this.withFunctionCode }, [Validators.required]],
reportStrategy: [{ value: null, disabled: !this.withReportStrategy }],
modifierType: [{ value: ModifierType.MULTIPLIER, disabled: true }],
modifierValue: [{ value: 1, disabled: true }, [Validators.pattern(nonZeroFloat)]],
id: [{value: id, disabled: true}],
@ -143,11 +154,26 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
}
applyKeysData(): void {
this.keysDataApplied.emit(this.mapKeysWithModifier());
this.keysDataApplied.emit(this.getFormValue());
}
private getFormValue(): ModbusValue[] {
return this.mapKeysWithModifier(
this.withReportStrategy
? this.cleanUpEmptyStrategies(this.keysListFormArray.value)
: this.keysListFormArray.value
);
}
private cleanUpEmptyStrategies(values: ModbusValue[]): ModbusValue[] {
return values.map((key) => {
const { reportStrategy, ...updatedKey } = key;
return !reportStrategy ? updatedKey : key;
});
}
private mapKeysWithModifier(): Array<ModbusValue> {
return this.keysListFormArray.value.map((keyData, i) => {
private mapKeysWithModifier(values: Array<ModbusFormValue>): Array<ModbusValue> {
return values.map((keyData, i) => {
if (this.showModifiersMap.get(this.keysListFormArray.controls[i].get('id').value)) {
const { modifierType, modifierValue, ...value } = keyData;
return modifierType ? { ...value, [modifierType]: modifierValue } : value;
@ -174,7 +200,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
}
private createDataKeyFormGroup(modbusValue: ModbusValue): FormGroup {
const { tag, value, type, address, objectsCount, functionCode, multiplier, divider } = modbusValue;
const { tag, value, type, address, objectsCount, functionCode, multiplier, divider, reportStrategy } = modbusValue;
const id = generateSecret(5);
const showModifier = this.shouldShowModifier(type);
@ -197,6 +223,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
[Validators.pattern(nonZeroFloat)]
],
id: [{ value: id, disabled: true }],
reportStrategy: [{ value: reportStrategy, disabled: !this.withReportStrategy }],
});
}

47
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts

@ -21,12 +21,13 @@ import {
Component,
ElementRef,
forwardRef,
Input,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { DialogService } from '@core/services/dialog.service';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, take, takeUntil } from 'rxjs/operators';
@ -38,6 +39,7 @@ import {
UntypedFormGroup,
} from '@angular/forms';
import {
LegacySlaveConfig,
ModbusMasterConfig,
ModbusProtocolLabelsMap,
ModbusSlaveInfo,
@ -49,6 +51,10 @@ import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { ModbusSlaveDialogComponent } from '../modbus-slave-dialog/modbus-slave-dialog.component';
import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract';
import { coerceBoolean } from '@shared/decorators/coercion';
import {
ModbusLegacySlaveDialogComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component';
@Component({
selector: 'tb-modbus-master-table',
@ -69,6 +75,9 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi
@ViewChild('searchInput') searchInputField: ElementRef;
@coerceBoolean()
@Input() isLegacy = false;
textSearchMode = false;
dataSource: SlavesDatasource;
masterFormGroup: UntypedFormGroup;
@ -152,14 +161,7 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi
}
const withIndex = isDefinedAndNotNull(index);
const value = withIndex ? this.slaves.at(index).value : {};
this.dialog.open<ModbusSlaveDialogComponent, ModbusSlaveInfo, ModbusValues>(ModbusSlaveDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
value,
buttonTitle: withIndex ? 'action.apply' : 'action.add'
}
}).afterClosed()
this.getSlaveDialog(value, withIndex ? 'action.apply' : 'action.add').afterClosed()
.pipe(take(1), takeUntil(this.destroy$))
.subscribe(res => {
if (res) {
@ -173,6 +175,33 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi
});
}
private getSlaveDialog(
value: LegacySlaveConfig | SlaveConfig,
buttonTitle: string
): MatDialogRef<ModbusLegacySlaveDialogComponent | ModbusSlaveDialogComponent> {
if (this.isLegacy) {
return this.dialog.open<ModbusLegacySlaveDialogComponent, ModbusSlaveInfo<LegacySlaveConfig>, ModbusValues>
(ModbusLegacySlaveDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
value: value as LegacySlaveConfig,
hideNewFields: true,
buttonTitle
}
});
}
return this.dialog.open<ModbusSlaveDialogComponent, ModbusSlaveInfo, ModbusValues>(ModbusSlaveDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
value: value as SlaveConfig,
buttonTitle,
hideNewFields: false,
}
});
}
deleteSlave($event: Event, index: number): void {
if ($event) {
$event.stopPropagation();

84
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts

@ -0,0 +1,84 @@
///
/// 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 { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import {
FormBuilder,
} from '@angular/forms';
import {
LegacySlaveConfig,
ModbusProtocolType,
ModbusSlaveInfo,
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { ModbusValuesComponent } from '../modbus-values/modbus-values.component';
import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe';
import {
ReportStrategyComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
import {
ModbusSlaveDialogAbstract
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract';
@Component({
selector: 'tb-modbus-legacy-slave-dialog',
templateUrl: './modbus-slave-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
CommonModule,
SharedModule,
ModbusValuesComponent,
ModbusSecurityConfigComponent,
GatewayPortTooltipPipe,
ReportStrategyComponent,
],
styleUrls: ['./modbus-slave-dialog.component.scss'],
})
export class ModbusLegacySlaveDialogComponent extends ModbusSlaveDialogAbstract<ModbusLegacySlaveDialogComponent, LegacySlaveConfig> {
constructor(
protected fb: FormBuilder,
protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo,
public dialogRef: MatDialogRef<ModbusLegacySlaveDialogComponent, LegacySlaveConfig>,
) {
super(fb, store, router, data, dialogRef);
}
protected override getSlaveResultData(): LegacySlaveConfig {
const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value;
const slaveResult = { ...rest, type, ...values };
if (type === ModbusProtocolType.Serial) {
slaveResult.port = serialPort;
}
return slaveResult;
}
protected override addFieldsToFormGroup(): void {
this.slaveConfigFormGroup.addControl('sendDataOnlyOnChange', this.fb.control(false));
}
}

207
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts

@ -0,0 +1,207 @@
///
/// 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 { Directive, Inject, OnDestroy } from '@angular/core';
import {
FormBuilder,
FormControl,
UntypedFormGroup,
Validators,
} from '@angular/forms';
import {
ModbusBaudrates,
ModbusByteSizes,
ModbusMethodLabelsMap,
ModbusMethodType,
ModbusOrderType,
ModbusParity,
ModbusParityLabelsMap,
ModbusProtocolLabelsMap,
ModbusProtocolType,
ModbusSerialMethodType,
ModbusSlaveInfo,
noLeadTrailSpacesRegex,
PortLimits,
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { Subject } from 'rxjs';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { takeUntil } from 'rxjs/operators';
import { isEqual } from '@core/utils';
import { helpBaseUrl } from '@shared/models/constants';
@Directive()
export abstract class ModbusSlaveDialogAbstract<Component, Config> extends DialogComponent<Component, Config> implements OnDestroy {
slaveConfigFormGroup: UntypedFormGroup;
showSecurityControl: FormControl<boolean>;
portLimits = PortLimits;
readonly modbusProtocolTypes = Object.values(ModbusProtocolType);
readonly modbusMethodTypes = Object.values(ModbusMethodType);
readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType);
readonly modbusParities = Object.values(ModbusParity);
readonly modbusByteSizes = ModbusByteSizes;
readonly modbusBaudrates = ModbusBaudrates;
readonly modbusOrderType = Object.values(ModbusOrderType);
readonly ModbusProtocolType = ModbusProtocolType;
readonly ModbusParityLabelsMap = ModbusParityLabelsMap;
readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap;
readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap;
readonly modbusHelpLink =
helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters';
private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict'];
private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host'];
private destroy$ = new Subject<void>();
constructor(
protected fb: FormBuilder,
protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo,
public dialogRef: MatDialogRef<Component, Config>,
) {
super(store, router, dialogRef);
this.showSecurityControl = this.fb.control(false);
this.initializeSlaveFormGroup();
this.updateSlaveFormGroup();
this.updateControlsEnabling(this.data.value.type);
this.observeTypeChange();
this.observeShowSecurity();
this.showSecurityControl.patchValue(!!this.data.value.security && !isEqual(this.data.value.security, {}));
}
get protocolType(): ModbusProtocolType {
return this.slaveConfigFormGroup.get('type').value;
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
cancel(): void {
this.dialogRef.close(null);
}
add(): void {
if (!this.slaveConfigFormGroup.valid) {
return;
}
this.dialogRef.close(this.getSlaveResultData());
}
private initializeSlaveFormGroup(): void {
this.slaveConfigFormGroup = this.fb.group({
name: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
type: [ModbusProtocolType.TCP],
host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]],
serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
method: [ModbusMethodType.SOCKET, [Validators.required]],
baudrate: [this.modbusBaudrates[0]],
stopbits: [1],
bytesize: [ModbusByteSizes[0]],
parity: [ModbusParity.None],
strict: [true],
unitId: [null, [Validators.required]],
deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
timeout: [35],
byteOrder: [ModbusOrderType.BIG],
wordOrder: [ModbusOrderType.BIG],
retries: [true],
retryOnEmpty: [true],
retryOnInvalid: [true],
pollPeriod: [5000, [Validators.required]],
connectAttemptTimeMs: [5000, [Validators.required]],
connectAttemptCount: [5, [Validators.required]],
waitAfterFailedAttemptsMs: [300000, [Validators.required]],
values: [{}],
security: [{}],
});
this.addFieldsToFormGroup();
}
private updateSlaveFormGroup(): void {
this.slaveConfigFormGroup.patchValue({
...this.data.value,
port: this.data.value.type === ModbusProtocolType.Serial ? null : this.data.value.port,
serialPort: this.data.value.type === ModbusProtocolType.Serial ? this.data.value.port : '',
values: {
attributes: this.data.value.attributes ?? [],
timeseries: this.data.value.timeseries ?? [],
attributeUpdates: this.data.value.attributeUpdates ?? [],
rpc: this.data.value.rpc ?? [],
}
});
}
private observeTypeChange(): void {
this.slaveConfigFormGroup.get('type').valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(type => {
this.updateControlsEnabling(type);
this.updateMethodType(type);
});
}
private updateMethodType(type: ModbusProtocolType): void {
if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) {
this.slaveConfigFormGroup.get('method').patchValue(
type === ModbusProtocolType.Serial
? ModbusSerialMethodType.ASCII
: ModbusMethodType.SOCKET,
{emitEvent: false}
);
}
}
private updateControlsEnabling(type: ModbusProtocolType): void {
const [enableKeys, disableKeys] = type === ModbusProtocolType.Serial
? [this.serialSpecificControlKeys, this.tcpUdpSpecificControlKeys]
: [this.tcpUdpSpecificControlKeys, this.serialSpecificControlKeys];
enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false }));
disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false }));
this.updateSecurityEnabling(this.showSecurityControl.value);
}
private observeShowSecurity(): void {
this.showSecurityControl.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(value => this.updateSecurityEnabling(value));
}
private updateSecurityEnabling(isEnabled: boolean): void {
if (isEnabled && this.protocolType !== ModbusProtocolType.Serial) {
this.slaveConfigFormGroup.get('security').enable({emitEvent: false});
} else {
this.slaveConfigFormGroup.get('security').disable({emitEvent: false});
}
}
protected abstract addFieldsToFormGroup(): void;
protected abstract getSlaveResultData(): Config;
}

7
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html

@ -227,13 +227,16 @@
</mat-form-field>
</div>
</div>
<div class="tb-form-row" fxLayoutAlign="space-between center">
<div *ngIf="data.hideNewFields else reportStrategy" class="tb-form-row" fxLayoutAlign="space-between center">
<mat-slide-toggle class="mat-slide" formControlName="sendDataOnlyOnChange">
<mat-label>
{{ 'gateway.send-data-on-change' | translate }}
</mat-label>
</mat-slide-toggle>
</div>
<ng-template #reportStrategy>
<tb-report-strategy formControlName="reportStrategy" [isExpansionMode]="true"/>
</ng-template>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>
@ -345,7 +348,7 @@
</mat-expansion-panel>
</div>
<div class="tb-form-panel stroked">
<tb-modbus-values [singleMode]="true" formControlName="values"></tb-modbus-values>
<tb-modbus-values [singleMode]="true" [hideNewFields]="data.hideNewFields" formControlName="values"></tb-modbus-values>
</div>
</div>
</div>

191
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts

@ -14,62 +14,35 @@
/// limitations under the License.
///
import { ChangeDetectionStrategy, Component, forwardRef, Inject, OnDestroy } from '@angular/core';
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import {
FormBuilder,
FormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormGroup,
Validators,
} from '@angular/forms';
import {
ModbusBaudrates,
ModbusByteSizes,
ModbusMethodLabelsMap,
ModbusMethodType,
ModbusOrderType,
ModbusParity,
ModbusParityLabelsMap,
ModbusProtocolLabelsMap,
ModbusProtocolType,
ModbusSerialMethodType,
ModbusSlaveInfo,
noLeadTrailSpacesRegex,
PortLimits,
SlaveConfig,
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs';
import { ModbusValuesComponent } from '../modbus-values/modbus-values.component';
import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe';
import { takeUntil } from 'rxjs/operators';
import { isEqual } from '@core/utils';
import { helpBaseUrl } from '@shared/models/constants';
import {
ReportStrategyComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
import {
ModbusSlaveDialogAbstract
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract';
@Component({
selector: 'tb-modbus-slave-dialog',
templateUrl: './modbus-slave-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ModbusSlaveDialogComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ModbusSlaveDialogComponent),
multi: true
}
],
standalone: true,
imports: [
CommonModule,
@ -77,70 +50,23 @@ import { helpBaseUrl } from '@shared/models/constants';
ModbusValuesComponent,
ModbusSecurityConfigComponent,
GatewayPortTooltipPipe,
ReportStrategyComponent,
],
styleUrls: ['./modbus-slave-dialog.component.scss'],
})
export class ModbusSlaveDialogComponent extends DialogComponent<ModbusSlaveDialogComponent, SlaveConfig> implements OnDestroy {
slaveConfigFormGroup: UntypedFormGroup;
showSecurityControl: FormControl<boolean>;
portLimits = PortLimits;
readonly modbusProtocolTypes = Object.values(ModbusProtocolType);
readonly modbusMethodTypes = Object.values(ModbusMethodType);
readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType);
readonly modbusParities = Object.values(ModbusParity);
readonly modbusByteSizes = ModbusByteSizes;
readonly modbusBaudrates = ModbusBaudrates;
readonly modbusOrderType = Object.values(ModbusOrderType);
readonly ModbusProtocolType = ModbusProtocolType;
readonly ModbusParityLabelsMap = ModbusParityLabelsMap;
readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap;
readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap;
readonly modbusHelpLink =
helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters';
private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict'];
private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host'];
private destroy$ = new Subject<void>();
export class ModbusSlaveDialogComponent extends ModbusSlaveDialogAbstract<ModbusSlaveDialogComponent, SlaveConfig> {
constructor(
private fb: FormBuilder,
protected fb: FormBuilder,
protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo,
public dialogRef: MatDialogRef<ModbusSlaveDialogComponent, SlaveConfig>,
) {
super(store, router, dialogRef);
this.showSecurityControl = this.fb.control(false);
this.initializeSlaveFormGroup();
this.updateSlaveFormGroup();
this.updateControlsEnabling(this.data.value.type);
this.observeTypeChange();
this.observeShowSecurity();
this.showSecurityControl.patchValue(!!this.data.value.security && !isEqual(this.data.value.security, {}));
}
get protocolType(): ModbusProtocolType {
return this.slaveConfigFormGroup.get('type').value;
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
cancel(): void {
this.dialogRef.close(null);
super(fb, store, router, data, dialogRef);
}
add(): void {
if (!this.slaveConfigFormGroup.valid) {
return;
}
protected override getSlaveResultData(): SlaveConfig {
const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value;
const slaveResult = { ...rest, type, ...values };
@ -148,97 +74,14 @@ export class ModbusSlaveDialogComponent extends DialogComponent<ModbusSlaveDialo
slaveResult.port = serialPort;
}
this.dialogRef.close(slaveResult);
}
private initializeSlaveFormGroup(): void {
this.slaveConfigFormGroup = this.fb.group({
name: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
type: [ModbusProtocolType.TCP],
host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]],
serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
method: [ModbusMethodType.SOCKET, [Validators.required]],
baudrate: [this.modbusBaudrates[0]],
stopbits: [1],
bytesize: [ModbusByteSizes[0]],
parity: [ModbusParity.None],
strict: [true],
unitId: [null, [Validators.required]],
deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
sendDataOnlyOnChange: [false],
timeout: [35],
byteOrder: [ModbusOrderType.BIG],
wordOrder: [ModbusOrderType.BIG],
retries: [true],
retryOnEmpty: [true],
retryOnInvalid: [true],
pollPeriod: [5000, [Validators.required]],
connectAttemptTimeMs: [5000, [Validators.required]],
connectAttemptCount: [5, [Validators.required]],
waitAfterFailedAttemptsMs: [300000, [Validators.required]],
values: [{}],
security: [{}],
});
}
private updateSlaveFormGroup(): void {
this.slaveConfigFormGroup.patchValue({
...this.data.value,
port: this.data.value.type === ModbusProtocolType.Serial ? null : this.data.value.port,
serialPort: this.data.value.type === ModbusProtocolType.Serial ? this.data.value.port : '',
values: {
attributes: this.data.value.attributes ?? [],
timeseries: this.data.value.timeseries ?? [],
attributeUpdates: this.data.value.attributeUpdates ?? [],
rpc: this.data.value.rpc ?? [],
}
});
}
private observeTypeChange(): void {
this.slaveConfigFormGroup.get('type').valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(type => {
this.updateControlsEnabling(type);
this.updateMethodType(type);
});
}
private updateMethodType(type: ModbusProtocolType): void {
if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) {
this.slaveConfigFormGroup.get('method').patchValue(
type === ModbusProtocolType.Serial
? ModbusSerialMethodType.ASCII
: ModbusMethodType.SOCKET,
{emitEvent: false}
);
if (!slaveResult.reportStrategy) {
delete slaveResult.reportStrategy;
}
}
private updateControlsEnabling(type: ModbusProtocolType): void {
const [enableKeys, disableKeys] = type === ModbusProtocolType.Serial
? [this.serialSpecificControlKeys, this.tcpUdpSpecificControlKeys]
: [this.tcpUdpSpecificControlKeys, this.serialSpecificControlKeys];
enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false }));
disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false }));
this.updateSecurityEnabling(this.showSecurityControl.value);
return slaveResult;
}
private observeShowSecurity(): void {
this.showSecurityControl.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(value => this.updateSecurityEnabling(value));
}
private updateSecurityEnabling(isEnabled: boolean): void {
if (isEnabled && this.protocolType !== ModbusProtocolType.Serial) {
this.slaveConfigFormGroup.get('security').enable({emitEvent: false});
} else {
this.slaveConfigFormGroup.get('security').disable({emitEvent: false});
}
protected override addFieldsToFormGroup(): void {
this.slaveConfigFormGroup.addControl('reportStrategy', this.fb.control(null));
}
}

6
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts

@ -87,6 +87,9 @@ export class ModbusValuesComponent implements ControlValueAccessor, Validator, O
@coerceBoolean()
@Input() singleMode = false;
@coerceBoolean()
@Input() hideNewFields = false;
disabled = false;
modbusRegisterTypes: ModbusRegisterType[] = Object.values(ModbusRegisterType);
modbusValueKeys = Object.values(ModbusValueKey);
@ -172,7 +175,8 @@ export class ModbusValuesComponent implements ControlValueAccessor, Validator, O
panelTitle: ModbusKeysPanelTitleTranslationsMap.get(keysType),
addKeyTitle: ModbusKeysAddKeyTranslationsMap.get(keysType),
deleteKeyTitle: ModbusKeysDeleteKeyTranslationsMap.get(keysType),
noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType)
noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType),
hideNewFields: this.hideNewFields,
};
const dataKeysPanelPopover = this.popoverService.displayPopover(
trigger,

57
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html

@ -0,0 +1,57 @@
<!--
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]="reportStrategyFormGroup" class="tb-form-panel stroked">
<mat-expansion-panel *ngIf="isExpansionMode else defaultMode" class="tb-settings" [expanded]="showStrategyControl.value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle fxLayoutAlign="center" [formControl]="showStrategyControl" class="mat-slide" (click)="$event.stopPropagation()">
<mat-label>
{{ 'gateway.report-strategy.label' | translate }}
</mat-label>
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-container [ngTemplateOutlet]="strategyFields"></ng-container>
</mat-expansion-panel>
<ng-template #defaultMode>
<div class="tb-form-panel-title" translate>gateway.report-strategy.label</div>
<ng-container [ngTemplateOutlet]="strategyFields"></ng-container>
</ng-template>
<ng-template #strategyFields>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width">{{ 'gateway.type' | translate }}</div>
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of reportStrategyTypes" [value]="type">{{ ReportTypeTranslateMap.get(type) | translate }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="reportStrategyFormGroup.get('type').value !== ReportStrategyType.OnChange" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width tb-required">
<span tbTruncateWithTooltip translate>
gateway.report-strategy.report-period
</span>
</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" name="value" formControlName="reportPeriod" placeholder="{{ 'gateway.set' | translate }}"/>
</mat-form-field>
</div>
</div>
</ng-template>
</div>

170
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts

@ -0,0 +1,170 @@
///
/// 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 {
ChangeDetectionStrategy,
Component,
forwardRef,
Input,
OnDestroy,
} from '@angular/core';
import { Subject } from 'rxjs';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormGroup,
ValidationErrors,
Validators
} from '@angular/forms';
import {
ReportStrategyConfig,
ReportStrategyType,
ReportStrategyTypeTranslationsMap
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { filter, takeUntil } from 'rxjs/operators';
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import {
ModbusSecurityConfigComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-report-strategy',
templateUrl: './report-strategy.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ReportStrategyComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ReportStrategyComponent),
multi: true
}
],
standalone: true,
imports: [
CommonModule,
SharedModule,
ModbusSecurityConfigComponent,
]
})
export class ReportStrategyComponent implements ControlValueAccessor, OnDestroy {
@coerceBoolean()
@Input() isExpansionMode = false;
reportStrategyFormGroup: UntypedFormGroup;
showStrategyControl: FormControl<boolean>;
readonly reportStrategyTypes = Object.values(ReportStrategyType);
readonly ReportTypeTranslateMap = ReportStrategyTypeTranslationsMap;
readonly ReportStrategyType = ReportStrategyType;
private onChange: (value: ReportStrategyConfig) => void;
private onTouched: () => void;
private destroy$ = new Subject<void>();
constructor(private fb: FormBuilder) {
this.showStrategyControl = this.fb.control(false);
this.reportStrategyFormGroup = this.fb.group({
type: [{ value: ReportStrategyType.OnReportPeriod, disabled: true }, []],
reportPeriod: [{ value: 5000, disabled: true }, [Validators.required]],
});
this.observeStrategyFormChange();
this.observeStrategyToggle();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
writeValue(reportStrategyConfig: ReportStrategyConfig): void {
if (this.isExpansionMode) {
this.showStrategyControl.setValue(!!reportStrategyConfig, {emitEvent: false});
}
if (reportStrategyConfig) {
this.reportStrategyFormGroup.enable({emitEvent: false});
}
const { type = ReportStrategyType.OnReportPeriod, reportPeriod = 5000 } = reportStrategyConfig ?? {};
this.reportStrategyFormGroup.setValue({ type, reportPeriod }, {emitEvent: false});
this.onTypeChange(type);
}
validate(): ValidationErrors | null {
return this.reportStrategyFormGroup.valid || this.reportStrategyFormGroup.disabled ? null : {
reportStrategyForm: { valid: false }
};
}
registerOnChange(fn: (value: ReportStrategyConfig) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
private observeStrategyFormChange(): void {
this.reportStrategyFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
this.onChange(value);
this.onTouched();
});
this.reportStrategyFormGroup.get('type').valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(type => this.onTypeChange(type));
}
private observeStrategyToggle(): void {
this.showStrategyControl.valueChanges
.pipe(takeUntil(this.destroy$), filter(() => this.isExpansionMode))
.subscribe(enable => {
if (enable) {
this.reportStrategyFormGroup.enable({emitEvent: false});
this.reportStrategyFormGroup.get('reportPeriod').addValidators(Validators.required);
this.onChange(this.reportStrategyFormGroup.value);
} else {
this.reportStrategyFormGroup.disable({emitEvent: false});
this.reportStrategyFormGroup.get('reportPeriod').removeValidators(Validators.required);
this.onChange(null);
}
this.reportStrategyFormGroup.updateValueAndValidity({emitEvent: false});
});
}
private onTypeChange(type: ReportStrategyType): void {
const reportPeriodControl = this.reportStrategyFormGroup.get('reportPeriod');
if (type === ReportStrategyType.OnChange) {
reportPeriodControl.disable({emitEvent: false});
} else if (!this.isExpansionMode || this.showStrategyControl.value) {
reportPeriodControl.enable({emitEvent: false});
}
}
}

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html

@ -299,5 +299,9 @@
</mat-label>
</mat-slide-toggle>
</div>
<tb-report-strategy
*ngIf="connectorForm.get('type').value === ConnectorType.MODBUS && connectorForm.get('configVersion').value === GatewayVersion.Current"
formControlName="reportStrategy"
/>
</section>
</ng-template>

17
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts

@ -297,12 +297,13 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
}
private hasSameConfig(sharedDataConfigJson: ConnectorBaseInfo, connectorDataConfigJson: ConnectorBaseInfo): boolean {
const { name, id, enableRemoteLogging, logLevel, configVersion, ...sharedDataConfig } = sharedDataConfigJson;
const { name, id, enableRemoteLogging, logLevel, reportStrategy, configVersion, ...sharedDataConfig } = sharedDataConfigJson;
const {
name: connectorName,
id: connectorId,
enableRemoteLogging: connectorEnableRemoteLogging,
logLevel: connectorLogLevel,
reportStrategy: connectorReportStrategy,
configVersion: connectorConfigVersion,
...connectorConfig
} = connectorDataConfigJson;
@ -352,7 +353,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
configuration: '',
configurationJson: {},
basicConfig: {},
configVersion: ''
configVersion: '',
reportStrategy: [{ value: {}, disabled: true }],
}, {emitEvent: false});
this.connectorForm.markAsPristine();
}
@ -543,6 +545,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
configurationJson: [{}, [Validators.required]],
basicConfig: [{}],
configVersion: [''],
reportStrategy: [{ value: {}, disabled: true }],
});
this.connectorForm.disable();
}
@ -752,6 +755,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
}
private updateConnector(connector: GatewayConnector): void {
this.toggleReportStrategy(connector.type);
switch (connector.type) {
case ConnectorType.MQTT:
case ConnectorType.OPCUA:
@ -771,6 +775,15 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
this.createJsonConfigWatcher();
}
private toggleReportStrategy(type: ConnectorType): void {
const reportStrategyControl = this.connectorForm.get('reportStrategy');
if (type === ConnectorType.MODBUS) {
reportStrategyControl.enable({emitEvent: false});
} else {
reportStrategyControl.disable({emitEvent: false});
}
}
private setClientData(data: PageData<GatewayAttributeData>): void {
if (this.initialConnector) {
const clientConnectorData = data.data.find(attr => attr.key === this.initialConnector.name);

43
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts

@ -210,6 +210,7 @@ export interface ConnectorBaseInfo {
enableRemoteLogging: boolean;
logLevel: GatewayLogLevel;
configVersion: string | number;
reportStrategy?: ReportStrategyConfig;
}
export type MQTTBasicConfig = MQTTBasicConfig_v3_5_2 | MQTTLegacyBasicConfig;
@ -256,7 +257,7 @@ export interface ModbusBasicConfig_v3_5_2 {
}
export interface ModbusLegacyBasicConfig {
master: ModbusMasterConfig;
master: ModbusMasterConfig<LegacySlaveConfig>;
slave: ModbusLegacySlave;
}
@ -621,9 +622,10 @@ export interface MappingInfo {
buttonTitle: string;
}
export interface ModbusSlaveInfo {
value: SlaveConfig;
export interface ModbusSlaveInfo<Slave = SlaveConfig> {
value: Slave;
buttonTitle: string;
hideNewFields: boolean;
}
export enum ConfigurationModes {
@ -637,6 +639,20 @@ export enum SecurityType {
CERTIFICATES = 'certificates'
}
export enum ReportStrategyType {
OnChange = 'ON_CHANGE',
OnReportPeriod = 'ON_REPORT_PERIOD',
OnChangeOrReportPeriod = 'ON_CHANGE_OR_REPORT_PERIOD'
}
export const ReportStrategyTypeTranslationsMap = new Map<ReportStrategyType, string>(
[
[ReportStrategyType.OnChange, 'gateway.report-strategy.on-change'],
[ReportStrategyType.OnReportPeriod, 'gateway.report-strategy.on-report-period'],
[ReportStrategyType.OnChangeOrReportPeriod, 'gateway.report-strategy.on-change-or-report-period']
]
);
export enum ModeType {
NONE = 'None',
SIGN = 'Sign',
@ -1154,8 +1170,12 @@ export const ModbusKeysNoKeysTextTranslationsMap = new Map<ModbusValueKey, strin
]
);
export interface ModbusMasterConfig {
slaves: SlaveConfig[];
export interface ModbusMasterConfig<Slave = SlaveConfig> {
slaves: Slave[];
}
export interface LegacySlaveConfig extends Omit<SlaveConfig, 'reportStrategy'> {
sendDataOnlyOnChange: boolean;
}
export interface SlaveConfig {
@ -1175,7 +1195,7 @@ export interface SlaveConfig {
unitId: number;
deviceName: string;
deviceType?: string;
sendDataOnlyOnChange: boolean;
reportStrategy: ReportStrategyConfig;
connectAttemptTimeMs: number;
connectAttemptCount: number;
waitAfterFailedAttemptsMs: number;
@ -1198,10 +1218,16 @@ export interface ModbusValue {
objectsCount: number;
address: number;
value?: string;
reportStrategy?: ReportStrategyConfig;
multiplier?: number;
divider?: number;
}
export interface ModbusFormValue extends ModbusValue {
modifierType?: ModifierType;
modifierValue?: string;
}
export interface ModbusSecurity {
certfile?: string;
keyfile?: string;
@ -1264,4 +1290,9 @@ export interface ModbusIdentity {
modelName?: string;
}
export interface ReportStrategyConfig {
type: ReportStrategyType;
reportPeriod?: number;
}
export const ModbusBaudrates = [4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600];

30
ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts

@ -15,6 +15,7 @@
///
import {
LegacySlaveConfig,
ModbusDataType,
ModbusLegacyRegisterValues,
ModbusLegacySlave,
@ -23,17 +24,36 @@ import {
ModbusSlave,
ModbusValue,
ModbusValues,
ReportStrategyType,
SlaveConfig
} from '@home/components/widget/lib/gateway/gateway-widget.models';
export class ModbusVersionMappingUtil {
static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig {
static mapMasterToUpgradedVersion(master: ModbusMasterConfig<LegacySlaveConfig>): ModbusMasterConfig {
return {
slaves: master.slaves.map((slave: SlaveConfig) => ({
...slave,
deviceType: slave.deviceType ?? 'default',
}))
slaves: master.slaves.map((slave: LegacySlaveConfig) => {
const { sendDataOnlyOnChange, ...restSlave } = slave;
return {
...restSlave,
deviceType: slave.deviceType ?? 'default',
reportStrategy: sendDataOnlyOnChange
? { type: ReportStrategyType.OnChange }
: { type: ReportStrategyType.OnReportPeriod, reportPeriod: slave.pollPeriod }
};
})
};
}
static mapMasterToDowngradedVersion(master: ModbusMasterConfig): ModbusMasterConfig<LegacySlaveConfig> {
return {
slaves: master.slaves.map((slave: SlaveConfig) => {
const { reportStrategy, ...restSlave } = slave;
return {
...restSlave,
sendDataOnlyOnChange: reportStrategy?.type !== ReportStrategyType.OnReportPeriod
};
})
};
}

11
ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts

@ -18,7 +18,7 @@ import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
ElementRef, NgZone,
OnDestroy,
OnInit,
Renderer2,
@ -72,7 +72,8 @@ export class PowerButtonWidgetComponent extends
constructor(protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer,
private renderer: Renderer2,
protected cd: ChangeDetectorRef) {
protected cd: ChangeDetectorRef,
protected zone: NgZone) {
super(cd);
}
@ -178,8 +179,10 @@ export class PowerButtonWidgetComponent extends
this.renderer.setStyle(this.svgShape.node, 'overflow', 'visible');
this.renderer.setStyle(this.svgShape.node, 'user-select', 'none');
this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape,
this.settings, this.value, this.disabledState, () => this.onClick());
this.zone.run(() => {
this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape,
this.settings, this.value, this.disabledState, () => this.onClick());
});
this.shapeResize$ = new ResizeObserver(() => {
this.onResize();

36
ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts

@ -72,9 +72,6 @@ export interface ScadaSymbolApi {
text: (element: Element | Element[], text: string) => void;
font: (element: Element | Element[], font: Font, color: string) => void;
icon: (element: Element | Element[], icon: string, size?: number, color?: string, center?: boolean) => void;
animate: (element: Element, duration: number) => Runner;
resetAnimation: (element: Element) => void;
finishAnimation: (element: Element) => void;
cssAnimate: (element: Element, duration: number) => ScadaSymbolAnimation;
cssAnimation: (element: Element) => ScadaSymbolAnimation | undefined;
resetCssAnimation: (element: Element) => void;
@ -657,9 +654,6 @@ export class ScadaSymbolObject {
text: this.setElementText.bind(this),
font: this.setElementFont.bind(this),
icon: this.setElementIcon.bind(this),
animate: this.animate.bind(this),
resetAnimation: this.resetAnimation.bind(this),
finishAnimation: this.finishAnimation.bind(this),
cssAnimate: this.cssAnimate.bind(this),
cssAnimation: this.cssAnimation.bind(this),
resetCssAnimation: this.resetCssAnimation.bind(this),
@ -731,8 +725,6 @@ export class ScadaSymbolObject {
const valueSetter = ValueSetter.fromSettings<any>(this.ctx, setValueSettings, this.simulated);
this.valueSetters[setBehavior.id] = valueSetter;
this.valueActions.push(valueSetter);
} else if (behavior.type === ScadaSymbolBehaviorType.widgetAction) {
// TODO:
}
}
this.renderState();
@ -980,30 +972,18 @@ export class ScadaSymbolObject {
fontSetClasses.forEach(className => textElement.addClass(className));
textElement.font({size: `${size}px`});
textElement.attr({
'text-anchor': 'start',
'dominant-baseline': 'hanging',
style: `font-size: ${size}px`
});
textElement.fill(color);
const tspan = textElement.first();
tspan.attr({
'text-anchor': 'start',
'dominant-baseline': 'hanging'
});
return of(textElement);
}
}
private animate(element: Element, duration: number): Runner {
this.finishAnimation(element);
return element.animate(duration, 0, 'now');
}
private resetAnimation(element: Element) {
element.timeline().stop();
element.timeline(new Timeline());
}
private finishAnimation(element: Element) {
element.timeline().finish();
element.timeline(new Timeline());
}
private cssAnimate(element: Element, duration: number): ScadaSymbolAnimation {
return this.cssAnimations.animate(element, duration);
}
@ -1515,7 +1495,11 @@ class CssScadaSymbolAnimation implements ScadaSymbolAnimation {
const transform = this._initialTransform;
for (const key of Object.keys(this._transform)) {
if (this._relative) {
transformed[key] = this.normFloat(transform[key] + this._transform[key]);
if (['scaleX', 'scaleY'].includes(key)) {
transformed[key] = this.normFloat(transform[key] * this._transform[key]);
} else {
transformed[key] = this.normFloat(transform[key] + this._transform[key]);
}
} else {
transformed[key] = this.normFloat(this._transform[key]);
}

4
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -167,6 +167,9 @@ import {
ModbusRpcParametersComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component';
import { RpcTemplateArrayViewPipe } from '@home/components/widget/lib/gateway/pipes/rpc-template-array-view.pipe';
import {
ReportStrategyComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
@NgModule({
declarations: [
@ -264,6 +267,7 @@ import { RpcTemplateArrayViewPipe } from '@home/components/widget/lib/gateway/pi
OpcRpcParametersComponent,
ModbusRpcParametersComponent,
RpcTemplateArrayViewPipe,
ReportStrategyComponent,
],
exports: [
EntitiesTableWidgetComponent,

31
ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts

@ -200,6 +200,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
onMouseDown(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.MOUSE_DOWN
@ -207,6 +210,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
onClicked(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.CLICKED
@ -214,6 +220,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
onContextMenu(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.CONTEXT_MENU
@ -221,6 +230,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
onEdit(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.EDIT
@ -228,6 +240,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
onReplaceReferenceWithWidgetCopy(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.REPLACE_REFERENCE_WITH_WIDGET_COPY
@ -235,6 +250,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
onExport(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.EXPORT
@ -242,6 +260,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
onRemove(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.REMOVE
@ -283,17 +304,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
functionPosition: (instance, helper, position) => {
const clientRect = helper.origin.getBoundingClientRect();
const container = parent.getBoundingClientRect();
position.coord.left = clientRect.right - position.size.width - container.left;
position.coord.left = Math.max(0,clientRect.right - position.size.width - container.left);
position.coord.top = position.coord.top - container.top;
position.target = clientRect.right;
const rightOverflow = container.right - (position.coord.left + position.size.width);
if (rightOverflow < 0) {
position.coord.left += rightOverflow;
}
const leftOverflow = container.left - position.coord.left;
if (leftOverflow > 0) {
position.coord.left += leftOverflow;
}
return position;
},
functionReady: (_instance, helper) => {

43
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts

@ -1235,49 +1235,6 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags
},
]
},
animate: {
meta: 'function',
description: 'Finishes any previous animation and starts new animation for SVG element.',
args: [
{
name: 'element',
description: 'SVG element',
type: 'Element'
},
{
name: 'duration',
description: 'Animation duration in milliseconds',
type: 'number'
}
],
return: {
description: 'Instance of SVG.Runner which has the same methods as any element and additional methods to control the runner.',
type: '<a href="https://svgjs.dev/docs/3.2/animating/#svg-runner">SVG.Runner</a>'
}
},
resetAnimation: {
meta: 'function',
description: 'Stops animation if any and restore SVG element initial state, resets animation timeline.',
args: [
{
name: 'element',
description: 'SVG element',
type: 'Element'
},
]
},
finishAnimation: {
meta: 'function',
description: 'Finishes animation if any, SVG element state updated according to the end animation values, ' +
'resets animation timeline.',
args: [
{
name: 'element',
description: 'SVG element',
type: 'Element'
},
]
},
generateElementId: {
meta: 'function',
description: 'Generates new unique element id.',

8
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts

@ -258,7 +258,11 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements
);
const scadaSymbolTagSettingsTooltip = tagSettingsButton.tooltipster('instance');
setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, scadaSymbolTagSettingsTooltip);
const scadaSymbolTagSettingsComponentRef =
setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, scadaSymbolTagSettingsTooltip);
scadaSymbolTagSettingsTooltip.on('ready', () => {
scadaSymbolTagSettingsComponentRef.instance.updateFunctionsState();
});
}
if (!this.symbolElement.readonly) {
@ -400,7 +404,7 @@ class ScadaSymbolTagSettingsComponent extends ScadaSymbolPanelComponent implemen
this.updateFunctionsState();
}
private updateFunctionsState() {
updateFunctionsState() {
this.hasStateRenderFunction = this.symbolElement.hasStateRenderFunction();
this.hasClickAction = this.symbolElement.hasClickAction();
}

3
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -46,6 +46,7 @@ import { MatChipGrid } from '@angular/material/chips';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
import { isArray } from 'lodash';
@Component({
selector: 'tb-entity-list',
@ -209,7 +210,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
}
validate(): ValidationErrors | null {
return this.entityListFormGroup.valid ? null : {
return (isArray(this.modelValue) && this.modelValue.length) || !this.required ? null : {
entities: {valid: false}
};
}

7
ui-ngx/src/app/shared/import-export/import-export.service.ts

@ -363,6 +363,7 @@ export class ImportExportService {
public exportEntity(entityData: EntityInfoData | RuleChainMetaData): void {
const id = (entityData as EntityInfoData).id ?? (entityData as RuleChainMetaData).ruleChainId;
let fileName = (entityData as EntityInfoData).name;
let preparedData;
switch (id.entityType) {
case EntityType.DEVICE_PROFILE:
@ -389,13 +390,13 @@ export class ImportExportService {
preparedData = this.prepareDashboardExport(entityData as Dashboard);
break;
case EntityType.CUSTOMER:
preparedData = this.prepareExport({...entityData, name: (entityData as Customer).title});
(entityData as EntityInfoData).name = (entityData as Customer).title;
fileName = (entityData as Customer).title;
preparedData = this.prepareExport(entityData);
break;
default:
preparedData = this.prepareExport(entityData);
}
this.exportToPc(preparedData, (entityData as EntityInfoData).name);
this.exportToPc(preparedData, fileName);
}
private exportSelectedWidgetsBundle(widgetsBundle: WidgetsBundle): void {

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

@ -3361,6 +3361,13 @@
"memory-storage": "Memory storage",
"sqlite": "SQLITE"
},
"report-strategy": {
"label": "Report strategy",
"on-change": "On value change",
"on-report-period": "On report period",
"on-change-or-report-period": "On value change or report period",
"report-period": "Report period"
},
"source-type": {
"msg": "Extract from message",
"topic": "Extract from topic",

8
ui-ngx/src/assets/locale/locale.constant-lt_LT.json

@ -1826,9 +1826,10 @@
"condition-duration-value-required": "Duration value is required.",
"condition-duration-time-unit-required": "Time unit is required.",
"advanced-settings": "Advanced settings",
"alarm-rule-details": "Details",
"alarm-rule-details-hint": "Hint: use <code><span style=\"color: #000;\">$&#123;</span>keyName<span style=\"color: #000;\">&#125;</span></code> to substitute values of the attribute or telemetry keys that are used in alarm rule condition.",
"add-alarm-rule-details": "Add details",
"alarm-rule-additional-info": "Papildoma informacija",
"edit-alarm-rule-additional-info": "Redaguoti papildomą informaciją",
"alarm-rule-additional-info-placeholder": "Pateikite savo komentarus ir patikslinimus čia, kad jie būtų rodomi pavojaus signalo detalių skiltyje „Papildoma informacija“.",
"alarm-rule-additional-info-hint": "Hint: use <code><span style=\"color: #000;\">$&#123;</span>keyName<span style=\"color: #000;\">&#125;</span></code> to substitute values of the attribute or telemetry keys that are used in alarm rule condition.",
"alarm-rule-mobile-dashboard": "Mobile dashboard",
"alarm-rule-mobile-dashboard-hint": "Used by mobile application as an alarm details dashboard",
"alarm-rule-no-mobile-dashboard": "No dashboard selected",
@ -1838,7 +1839,6 @@
"propagate-alarm-to-owner": "Propagate alarm to entity owner (Customer or Tenant)",
"propagate-alarm-to-owner-hierarchy": "Propagate alarm to entity owners hierarchy",
"propagate-alarm-to-tenant": "Propagate alarm to Tenant",
"alarm-details": "Alarm details",
"alarm-rule-condition": "Alarm rule condition",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
"edit-alarm-rule-condition": "Edit alarm rule condition",

386
ui-ngx/src/assets/locale/locale.constant-nl_BE.json

File diff suppressed because it is too large

8
ui-ngx/src/assets/locale/locale.constant-pl_PL.json

@ -1832,9 +1832,10 @@
"condition-duration-value-required": "Wartość czasu trwania jest wymagana.",
"condition-duration-time-unit-required": "Jednostka czasu jest wymagana.",
"advanced-settings": "Zaawansowane ustawienia",
"alarm-rule-details": "Szczegóły",
"alarm-rule-details-hint": "Wskazówka: użyj <code><span style=\"color: #000;\">$&#123</span>NazwaKlucza<span style=\"color: #000;\">&#125</span></code> w celu zastąpienia wartości atrybutu lub kluczy telemetrycznych używanych w warunku reguły alarmowej.",
"add-alarm-rule-details": "Dodaj szczegóły",
"alarm-rule-additional-info": "Szczegóły",
"edit-alarm-rule-additional-info": "Extra informatie bewerken",
"alarm-rule-additional-info-placeholder": "Komentarze i poprawki należy wprowadzić tutaj, aby wyświetlić je w szczegółach alarmu w sekcji Dodatkowe informacje.",
"alarm-rule-additional-info-hint": "Wskazówka: użyj <code><span style=\"color: #000;\">$&#123;</span>NazwaKlucza<span style=\"color: #000;\">&#125</span></code> w celu zastąpienia wartości atrybutu lub kluczy telemetrycznych używanych w warunku reguły alarmowej.",
"alarm-rule-mobile-dashboard": "Mobilny panel",
"alarm-rule-mobile-dashboard-hint": "Używany przez aplikację mobilną jako panel ze szczegółami alarmów",
"alarm-rule-no-mobile-dashboard": "Nie wybrano żadnego panelu",
@ -1844,7 +1845,6 @@
"propagate-alarm-to-owner": "Prześlij alarm do właściciela obiektu (Klienta lub Najemcy)",
"propagate-alarm-to-owner-hierarchy": "Propaguj alarm w hierarchii właścicieli jednostek",
"propagate-alarm-to-tenant": "Przekaż alarm do Najemcy",
"alarm-details": "Szczegóły alarmu",
"alarm-rule-condition": "Warunek reguły alarmowej",
"enter-alarm-rule-condition-prompt": "Dodaj warunek reguły alarmowej",
"edit-alarm-rule-condition": "Edytuj warunek reguły alarmowej",

1
ui-ngx/src/assets/metadata/connector-default-configs/modbus.json

@ -18,7 +18,6 @@
"unitId": 1,
"deviceName": "Temp Sensor",
"deviceType": "default",
"sendDataOnlyOnChange": true,
"connectAttemptTimeMs": 5000,
"connectAttemptCount": 5,
"waitAfterFailedAttemptsMs": 300000,

3
ui-ngx/src/styles.scss

@ -912,9 +912,6 @@ mat-icon {
&.tb-mat-28 {
@include tb-mat-icon-size(28);
}
&.tb-mat-30 {
@include tb-mat-icon-size(30);
}
&.tb-mat-32 {
@include tb-mat-icon-size(32);
}

Loading…
Cancel
Save