Making Linear Gauge in DB2 Template Node

Hi all,
I am trying to make a very simple linear gauge in DB2 following the recommendations of the DB2 documentation. With a lot of guesswork, I have gotten Vuetify to make a simple set of 4 stacked "gauges". I have 4 different, asynchronous inputs. I am trying to update just one of these inputs at a time. If I send the info for just one input, however, the table collapses to just a single row for that item. I tried sending the entire table, but without values for the other inputs, and that approach does keep four rows, but doesn't show the graphs. Does anyone know a simple way to update a single value in a table, once it has been set, without resending all of the values (which is difficult, as the data is asynchronous, and I might need to wait a LONG time for the other values to update)? Thanks!

[{"id":"618032aab4e45f44","type":"inject","z":"d20a6f57ce52354d","name":"Initialize Table/Guage","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"headers\":[{\"title\":\"Tank\",\"value\":\"Tank\"},{\"title\":\"Current\",\"value\":\"current\"}],\"items\":[{\"Tank\":\"Black\",\"Type\":\"filling\",\"target\":80,\"current\":20},{\"Tank\":\"Fresh\",\"Type\":\"emptying\",\"target\":20,\"current\":18},{\"Tank\":\"Gray\",\"Type\":\"filling\",\"target\":80,\"current\":85},{\"Tank\":\"Propane\",\"Type\":\"emptying\",\"target\":20,\"current\":25}]}","payloadType":"json","x":520,"y":440,"wires":[["07676cb3ae5fec85"]]},{"id":"07676cb3ae5fec85","type":"ui-template","z":"d20a6f57ce52354d","group":"fa6f27a81c4d2539","page":"","ui":"","name":"","order":0,"width":0,"height":0,"head":"","format":"<template>\n  <v-data-table \n    :headers=\"msg.payload.headers\"\n    :items=\"msg.payload.items\"\n    density=\"compact\"\n    items-per-page=\"-1\">\n\n    <template v-slot:header.current>\n      <!-- Override how we render the header for the \"current\" column -->\n      <div class=\"text-center\">Current Level</div>\n    </template>\n    \n    <template v-slot:item.target=\"{ item }\">\n      <!-- Add a custom suffix to the value for the \"target\" column -->\n      {{ item.target }}%\n    </template>\n\n    <template v-slot:item.current=\"{ item }\">\n      <!-- Render a Linear Progress Bar for the \"current\" column -->\n      <v-progress-linear v-model=\"item.current\" min=\"0\" max=\"100\" height=\"25\" :color=\"getColor(item)\">\n        <template v-slot:default=\"{ value }\">\n          <strong>{{ item.current }}%</strong>\n        </template>\n      </v-progress-linear>\n    </template>\n  <template #bottom></template>\n  </v-data-table>\n</template>\n\n<script>\n    export default {\n    data () {\n      return {\n        search: ''\n      }\n    },\n    methods: {\n        // add a function to determine the color of the progress bar given the row's item\n      getColor: function (item) {\n        if ((item.Type == 'filling') && (item.current > item.target)) {\n          return 'red'\n        } else if ((item.Type == 'filling') && (item.current <= item.target)) {\n          return 'green'\n        } else if ((item.Type == 'emptying') && (item.current < item.target)) {\n          return 'red'\n        }else{\n          return 'green'\n        }\n      }\n    }\n  }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":820,"y":440,"wires":[[]]},{"id":"0ea738037067547d","type":"inject","z":"d20a6f57ce52354d","name":"Update","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"headers\":[{\"title\":\"Tank\",\"value\":\"Tank\"},{\"title\":\"Current\",\"value\":\"current\"}],\"items\":[{\"Tank\":\"Propane\",\"Type\":\"emptying\",\"target\":20,\"current\":15}]}","payloadType":"json","x":550,"y":480,"wires":[["07676cb3ae5fec85"]]},{"id":"fa6f27a81c4d2539","type":"ui-group","name":"Tank Status","page":"b74745b3765efdef","width":"6","height":"1","order":-1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"b74745b3765efdef","type":"ui-page","name":"Tank Status Page","ui":"f2eea23e252f30f6","path":"/page2","icon":"home","layout":"grid","theme":"a965ccfef139317a","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"f2eea23e252f30f6","type":"ui-base","name":"Dolores DB2","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false},{"id":"a965ccfef139317a","type":"ui-theme","name":"HN Theme","colors":{"surface":"#5c5c5c","primary":"#00fdff","bgPage":"#383838","groupBg":"#4f4f4f","groupOutline":"#858585"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

You could either make separate templates and pass only the data required for each one.

OR,

Add a local data property & use that for populating the table rows.
Add a watch on msg and scan the data for presence of each item of interest and update parts of the local data property according.

You could also change the way you input the data, using a join to send the data each time an element is received.
e.g.

[{"id":"618032aab4e45f44","type":"inject","z":"d1395164b4eec73e","name":"Initialize Table/Guage","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"[{\"Tank\":\"Black\",\"Type\":\"filling\",\"target\":80,\"current\":20},{\"Tank\":\"Fresh\",\"Type\":\"emptying\",\"target\":20,\"current\":18},{\"Tank\":\"Gray\",\"Type\":\"filling\",\"target\":80,\"current\":100},{\"Tank\":\"Propane\",\"Type\":\"emptying\",\"target\":20,\"current\":50}]","payloadType":"json","x":360,"y":6280,"wires":[["5e05e6041595277a"]]},{"id":"5e05e6041595277a","type":"split","z":"d1395164b4eec73e","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":530,"y":6280,"wires":[["1a6522db67d5f587"]]},{"id":"1a6522db67d5f587","type":"join","z":"d1395164b4eec73e","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"payload.Tank","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"4","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":530,"y":6340,"wires":[["1908cea06d9da415"]]},{"id":"3496ec4c393031f4","type":"inject","z":"d1395164b4eec73e","name":"Grayc 70","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Tank\":\"Gray\",\"Type\":\"filling\",\"target\":80,\"current\":70}","payloadType":"json","x":340,"y":6380,"wires":[["1a6522db67d5f587"]]},{"id":"e77e311b93d84c47","type":"inject","z":"d1395164b4eec73e","name":"Gray 80","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Tank\":\"Gray\",\"Type\":\"filling\",\"target\":80,\"current\":80}","payloadType":"json","x":330,"y":6420,"wires":[["1a6522db67d5f587"]]},{"id":"13a40e020fc0a6cd","type":"inject","z":"d1395164b4eec73e","name":"Propane 25","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Tank\":\"Propane\",\"Type\":\"emptying\",\"target\":20,\"current\":25}","payloadType":"json","x":370,"y":6500,"wires":[["1a6522db67d5f587"]]},{"id":"dfb8c676fd167076","type":"inject","z":"d1395164b4eec73e","name":"Propane 15","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Tank\":\"Propane\",\"Type\":\"emptying\",\"target\":20,\"current\":15}","payloadType":"json","x":370,"y":6540,"wires":[["1a6522db67d5f587"]]},{"id":"1908cea06d9da415","type":"change","z":"d1395164b4eec73e","name":"","rules":[{"t":"set","p":"order","pt":"msg","to":"[\"Black\",\"Fresh\",\"Gray\",\"Propane\"]","tot":"json"},{"t":"set","p":"payload","pt":"msg","to":"{\t   \"items\": $$.order.$lookup($$.payload, $)\t}","tot":"jsonata"},{"t":"set","p":"payload.headers","pt":"msg","to":" [         {             \"title\": \"Tank\",             \"value\": \"Tank\"         },         {             \"title\": \"Current\",             \"value\": \"current\"         }     ]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":6340,"wires":[["f660403e7839a956","07676cb3ae5fec85"]]},{"id":"f660403e7839a956","type":"debug","z":"d1395164b4eec73e","name":"debug 2474","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":790,"y":6240,"wires":[]},{"id":"07676cb3ae5fec85","type":"ui-template","z":"d1395164b4eec73e","group":"fa6f27a81c4d2539","page":"","ui":"","name":"","order":0,"width":0,"height":0,"head":"","format":"<template>\n  <v-data-table \n    :headers=\"msg.payload.headers\"\n    :items=\"msg.payload.items\"\n    density=\"compact\"\n    items-per-page=\"-1\">\n\n    <template v-slot:header.current>\n      <!-- Override how we render the header for the \"current\" column -->\n      <div class=\"text-center\">Current Level </div>\n    </template>\n    \n    <template v-slot:item.target=\"{ item }\">\n      <!-- Add a custom suffix to the value for the \"target\" column -->\n      {{ item.target }}%\n    </template>\n\n    <template v-slot:item.current=\"{ item }\">\n      <!-- Render a Linear Progress Bar for the \"current\" column -->\n      <v-progress-linear v-model=\"item.current\" min=\"0\" max=\"100\" height=\"25\" :color=\"getColor(item)\">\n        <template v-slot:default=\"{ value }\">\n          <strong>{{ item.current }}%</strong>\n        </template>\n      </v-progress-linear>\n    </template>\n  <template #bottom></template>\n  </v-data-table>\n</template>\n\n<script>\n    export default {\n    data () {\n      return {\n        search: ''\n      }\n    },\n    methods: {\n        // add a function to determine the color of the progress bar given the row's item\n      getColor: function (item) {\n        if ((item.Type == 'filling') && (item.current > item.target)) {\n          return 'red'\n        } else if ((item.Type == 'filling') && (item.current <= item.target)) {\n          return 'green'\n        } else if ((item.Type == 'emptying') && (item.current < item.target)) {\n          return 'red'\n        }else{\n          return 'green'\n        }\n      }\n    }\n  }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":860,"y":6340,"wires":[[]]},{"id":"fa6f27a81c4d2539","type":"ui-group","name":"Tank Status","page":"b74745b3765efdef","width":"6","height":"1","order":-1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"b74745b3765efdef","type":"ui-page","name":"Tank Status Page","ui":"f2eea23e252f30f6","path":"/page2","icon":"home","layout":"grid","theme":"a965ccfef139317a","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"f2eea23e252f30f6","type":"ui-base","name":"Dolores DB2","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false},{"id":"a965ccfef139317a","type":"ui-theme","name":"HN Theme","colors":{"surface":"#5c5c5c","primary":"#00fdff","bgPage":"#383838","groupBg":"#4f4f4f","groupOutline":"#858585"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

I added an ordering array also.

1 Like

Wow! Thank you, this is exactly what I was looking for. This practical answer really answers the mail, and demonstrates some powerful black magic in the ordering change node. I learned a lot from looking at this, like how to separate the headers from the data (I tried a lot of guesswork, but this example nails it). I did have one question. I thought the split data did not persist at the join node - why is it when you inject the gray or propane, the fresh and black are still at the input, waiting to be joined? I would have thought you would need to press the "initialize" a second time. Obviously you dont, but why? THANK YOU

The check box "And every subsequence message" will store all incoming and send them again every time a new payload topic arrives.

I added this for an easy option, but Steve's suggestion would be the best way to do it.

Hi Steve, thanks for the ideas!

I can see how you could make a set of four tables, and then plot each one (your suggestion 1). I am not quite advanced enough to understand your second suggestion, the local data property. Any chance you could aid my NR/JS education by providing an example?

My sincere thanks, and this is an awesome forum!

Thanks for the explanation! So how does the join node know to which input to use? Does it always replace the matching index with the newest time stamp? I am guessing these questions are pretty obvious. I have read the documentation, but find it just terse enough that I am not clear on what is happening. Thanks again in advance.

Curious, why is Steve's way better? Seems like it is a lot more code (though I don't quite understand it yet). Is it less memory intensive? Or faster?

The join node uses msg.payload.Tank as the property name for the payload, as specified in the join node config. Add debug nodes set to complete message object everywhere and look at the msg properties and the object the join node creates, it may help you understand what is used, and what it creates.

Steve's suggestion of using a watch and storing the incoming data locally would use the browser and not node-red to manipulate that data, taking the load off node-red (not that there is a great deal of load)