Ui Builder - eCharts with Vue caching problem

Hi everyone, I am using eCharts with UI builder and vuejs for graphics. I use and caching function also because I want to see the graphic from different clients/browsers. My problem is that the arrays that i use for X and Y axes get doubles when the browser is refreshed.
Here is my flow:

[{"id":"75668888.8cd508","type":"debug","z":"3aecfa59.984b76","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":990,"y":140,"wires":[]},{"id":"6334513b.b5fb2","type":"debug","z":"3aecfa59.984b76","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1070,"y":300,"wires":[]},{"id":"2a25f279.c41bfe","type":"uibuilder","z":"3aecfa59.984b76","name":"","topic":"","url":"chartEcharts","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":810,"y":200,"wires":[["75668888.8cd508"],["6334513b.b5fb2","f1b1f460.632128"]]},{"id":"38b1eb76.194b14","type":"link in","z":"3aecfa59.984b76","name":"echart-replay","links":["f1b1f460.632128"],"x":495,"y":200,"wires":[["60868209.24110c"]]},{"id":"f1b1f460.632128","type":"link out","z":"3aecfa59.984b76","name":"echart-controls","links":["38b1eb76.194b14"],"x":915,"y":280,"wires":[]},{"id":"5622596c.b04448","type":"influxdb in","z":"3aecfa59.984b76","influxdb":"6f1ec63.91ef438","name":"select total power","query":"select  * from \"sm-0001\" ORDER BY desc LIMIT 2","rawOutput":false,"precision":"s","retentionPolicy":"","x":170,"y":140,"wires":[["30d51a09.c50f26","a730a2a1.03487"]]},{"id":"9b77f2ca.28907","type":"inject","z":"3aecfa59.984b76","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":220,"wires":[["5622596c.b04448"]]},{"id":"30d51a09.c50f26","type":"function","z":"3aecfa59.984b76","name":"","func":"\nvar myArr = []\nvar valueArr = []\n\nfor (i = 0; i < msg.payload.length; i++ )\n{\n    valueArr.push(msg.payload[i].value)\n}\nmsg.topic = \"test1/test1\"\n\nmsg.payload = valueArr\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":380,"y":160,"wires":[["60868209.24110c"]]},{"id":"a730a2a1.03487","type":"function","z":"3aecfa59.984b76","name":"","func":"\nvar myArr = []\nvar valueArr = []\n\nfor (i = 0; i < msg.payload.length; i++ )\n{\n    valueArr.push(msg.payload[i].time)\n}\nmsg.topic = \"test2/test2\"\nmsg.payload = valueArr\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":320,"y":220,"wires":[["65870d8a.420294","60868209.24110c"]]},{"id":"65870d8a.420294","type":"debug","z":"3aecfa59.984b76","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":700,"y":320,"wires":[]},{"id":"60868209.24110c","type":"function","z":"3aecfa59.984b76","name":"","func":"// Expects input msgs with topic set \n\n// saved context\nvar homeMsgs = context.get('homeMsgs') || {}\n\n// Only send to single client if needed\nvar socketId = null\nif ( msg.hasOwnProperty('_socketId') ) {\n    socketId = msg._socketId\n}\n\n// Replay cache if requested\nif ( msg.hasOwnProperty('cacheControl') && msg.cacheControl === 'REPLAY' ) {\n    for (var topic in homeMsgs) {\n        let newMsg = {\n            \"topic\": topic, \n            \"payload\": homeMsgs[topic]\n        }\n        // Only send to a single client if we can\n        if ( socketId !== null ) newMsg._socketId = socketId\n        node.send(newMsg)\n    }\n    return null\n}\n// -- else --\n// Empty cache if requested\nif ( (msg.hasOwnProperty('cacheControl') && msg.cacheControl === 'RESET')  ||\n     (msg.payload.hasOwnProperty('cacheControl') && msg.payload.cacheControl === 'RESET') ) {\n    homeMsgs = {}\n    context.set('homeMsgs', homeMsgs)\n    return null\n}\n// -- else --\n\n// ignore cacheControl and uibuilder control messages\nif ( msg.hasOwnProperty('cacheControl') || msg.hasOwnProperty('uibuilderCtrl') ) return null\n\n// Add a counter for each device name\nif ( msg.topic.endsWith('$name') ) {\n    let topic = msg.topic.replace('$name', '$count')\n    let count = homeMsgs[topic] || 0\n    count = count + 1\n    homeMsgs[topic] = count\n    let newMsg = {\n        \"topic\": topic, \n        \"payload\": count\n    }\n    // Only send to a single client if we can\n    if ( socketId !== null ) newMsg._socketId = socketId\n    node.send(newMsg)\n}\n\n// Keep the last msg.payload by topic\nhomeMsgs[msg.topic] = msg.payload\n\n// save context for next time\ncontext.set('homeMsgs', homeMsgs)\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":640,"y":140,"wires":[["2a25f279.c41bfe"]]},{"id":"6f1ec63.91ef438","type":"influxdb","z":"","hostname":"159.89.103.242","port":"8086","protocol":"http","database":"smartmeters","name":"","usetls":false,"tls":""}]

Thank you very much for your help!

I think that you will need to expand on this as it isn't clear. Perhaps you could also post a screenshot of your flow as I can't copy/paste flows every time someone posts one I'm afraid.

Thank you for the great UI Builder first. I am attaching screenshot of the flow.
The first two function nodes just separate the data to two arrays and add different topics to them (dates for x axis and values for y axis. At the screenshot I captured the debug window too).
The third function node is the caching node.

In the index.js:
In the uibuilder.onChange function i have:

if (newVal.topic) {
                let splitTopic = newVal.topic.split('/')
                
                if (splitTopic.length === 2) {
                    
                    if (splitTopic[0].toLowerCase() === 'test1') {
                        
                        for (var i=0; i<newVal.payload.length; i++ )
                        {
                           
                        data.push(newVal.payload[i])
                        }
        
                    }
                    else
                    {
                       
                   for (var j=0; j<newVal.payload.length; j++ )
                        {
                      
                                var str = new Array(newVal.payload[j])
                                str = str.toString()
                                
                                str = str.slice(0, -5)
                                
                                str = str.replace('T', '\n')
                                
                                date.push(str)
                        }
                    }
                }
                
        }

Here I just push the two arrays in to the needed arrays that are defined as follow:

Vue.component('v-chart', VueECharts)

var date = [];

var data = [];


var app1 = new Vue({
    el: '#app',
    data: {
        // Data for bar chart
    ecOptionsBar: {    
    
    xAxis: {
        type: 'category',
        data: date ,       
    },
    
    
    series: [
        {
           
            data: data
        }
    ]
        },

    },

Than you very much for your time!

Hello Georgi,

I couldnt make anything from importing your flow either since it is depended on influxdb.
We dont have you db so it would be better if you pasted a msg going in the uibuilder node.

I understand you check the topic of the msg with all that logic (splitting, lowercase etc) .. all good .. although im not sure if its necessary to do all that at the front-end level. In my opinion the msg should have been correctly formatted from the node-red side (back-end).

there is no need to define var date = []; var data = []; outside of Vue

Vue.component('v-chart', VueECharts)

// var date = [];
//var data = [];


var app1 = new Vue({
    el: '#app',
    data: {
        // Data for bar chart
    ecOptionsBar: {    
    
    xAxis: {
        type: 'category',
        data: [],      // initialize it as empty array here
    },
    
    series: [
        {
            data: []   // initialize it as empty array here
        }
    ]
        },

    },

and instead of

shouldnt that be vueApp.ecOptionsBar.series[0].data.push(newVal.payload)

as described in the Wiki : Integrating-Vue-ECharts

also why did you split the msg coming out of your db ? :grinning:
.. one fn creating the values and one for the dates ?
could you do an array of objects in one function for example :

[
{dt: "2021-01-08 11:00:00", value: 2.56  },
{dt: "2021-01-08 12:00:00", value: 4.51  },
{dt: "2021-01-08 13:00:00", value: 5.34  }
]
1 Like

With VueJS, it is particularly important to define complex object variables as completely as possible up front. That is because if you don't, there is a danger that Vue will not be able to make it fully responsive and you will end up having to do Vue.set functions.

Whether you do processing in the front-end (browser) or back-end (Node-RED) is something of an art to balance. A rule of thumb would be that if the data would have to be processed by multiple browsers, probably better to do it once in the back-end. On the other hand, your back-end server may well be less powerful than your client devices (a Pi vs a PC for example) so that should be considered as well.

Thank you for your answer. In the UI builder node it goes two arrays (you can see them in the screenshot - NR debug area).
I split the message from the db, because I need two separate arrays for X and Y axis:

ecOptionsBar: {    
    
    xAxis: {
        type: 'category',
        data: [],      // initialize it as empty array here
    },
    
    series: [
        {
            data: []   // initialize it as empty array here
        }
    ]
}

Thank you for your answer. Maybe I wasn't clear enough in my previous post. The problem actually occur when i refresh the browser. I am attaching two screenshots, before and after browser refresh. Before browser refresh everything is ok, after refreshing the browser the points on the X axis are doubled.

Without a copy of the before and after data it is hard to assess. I would recommend capturing a typical output from your InfluxDB query then putting that into an inject node. Then you have repeatable data that can also be shared here on the forum.

1 Like

most probably something gets messed up in your Context Data (the data gets duplicated there) and when you refresh the page automatically a "REPLAY" cache is being sent and duplicate data gets plotted on the chart. Check your Context data for duplicates before and after a refresh.

image

but why do you even need the replay Cache feature - in your case - since you have the data in the influxDb database :wink:

Do a msg.query to influxdb node to get the data you need -> restructure it ready for the chart -> send it to uibuilder node -> uibuilder plots it to the chart after it receives it in onChange

as Julian said .. without a sample msg from the db its hard to replicate this and help further.

The cache is needed in case someone refreshes the page or you leave it up on a machine that goes to sleep.

uibuilder sends out a control msg when a client (re)joins, you can use that to trigger a cache resend but you could equally use it to re-do an InfluxDB query instead. Having a cache is likely to be quicker to draw the data though. Still, you probably wouldn't notice the difference.

I agree .. i played with the Cache example and its very useful ..
i just dont understand how Georgi implemented it in his flow :wink:

I put the output from the Influx into inject node as your suggestion (I use a query with only two resulsts for simplyest). It plots on the graph as expected, but if you hit refresh the points are doubled.
Thank you very much for your time!

[{"id":"2a25f279.c41bfe","type":"uibuilder","z":"3aecfa59.984b76","name":"","topic":"","url":"chartEcharts","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":650,"y":120,"wires":[[],["f1b1f460.632128"]]},{"id":"38b1eb76.194b14","type":"link in","z":"3aecfa59.984b76","name":"echart-replay","links":["f1b1f460.632128"],"x":475,"y":200,"wires":[["60868209.24110c"]]},{"id":"f1b1f460.632128","type":"link out","z":"3aecfa59.984b76","name":"echart-controls","links":["38b1eb76.194b14"],"x":755,"y":200,"wires":[]},{"id":"30d51a09.c50f26","type":"function","z":"3aecfa59.984b76","name":"","func":"\nvar myArr = []\nvar valueArr = []\n\nfor (i = 0; i < msg.payload.length; i++ )\n{\n    valueArr.push(msg.payload[i].value)\n}\nmsg.topic = \"test1/test1\"\n\nmsg.payload = valueArr\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":300,"y":80,"wires":[["60868209.24110c"]]},{"id":"a730a2a1.03487","type":"function","z":"3aecfa59.984b76","name":"","func":"\nvar myArr = []\nvar valueArr = []\n\nfor (i = 0; i < msg.payload.length; i++ )\n{\n    valueArr.push(msg.payload[i].time)\n}\nmsg.topic = \"test2/test2\"\nmsg.payload = valueArr\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":300,"y":180,"wires":[["60868209.24110c"]]},{"id":"60868209.24110c","type":"function","z":"3aecfa59.984b76","name":"","func":"// Expects input msgs with topic set \n\n// saved context\nvar homeMsgs = context.get('homeMsgs') || {}\n\n// Only send to single client if needed\nvar socketId = null\nif ( msg.hasOwnProperty('_socketId') ) {\n    socketId = msg._socketId\n}\n\n// Replay cache if requested\nif ( msg.hasOwnProperty('cacheControl') && msg.cacheControl === 'REPLAY' ) {\n    for (var topic in homeMsgs) {\n        let newMsg = {\n            \"topic\": topic, \n            \"payload\": homeMsgs[topic]\n        }\n        // Only send to a single client if we can\n        if ( socketId !== null ) newMsg._socketId = socketId\n        node.send(newMsg)\n    }\n    return null\n}\n// -- else --\n// Empty cache if requested\nif ( (msg.hasOwnProperty('cacheControl') && msg.cacheControl === 'RESET')  ||\n     (msg.payload.hasOwnProperty('cacheControl') && msg.payload.cacheControl === 'RESET') ) {\n    homeMsgs = {}\n    context.set('homeMsgs', homeMsgs)\n    return null\n}\n// -- else --\n\n// ignore cacheControl and uibuilder control messages\nif ( msg.hasOwnProperty('cacheControl') || msg.hasOwnProperty('uibuilderCtrl') ) return null\n\n// Add a counter for each device name\nif ( msg.topic.endsWith('$name') ) {\n    let topic = msg.topic.replace('$name', '$count')\n    let count = homeMsgs[topic] || 0\n    count = count + 1\n    homeMsgs[topic] = count\n    let newMsg = {\n        \"topic\": topic, \n        \"payload\": count\n    }\n    // Only send to a single client if we can\n    if ( socketId !== null ) newMsg._socketId = socketId\n    node.send(newMsg)\n}\n\n// Keep the last msg.payload by topic\nhomeMsgs[msg.topic] = msg.payload\n\n// save context for next time\ncontext.set('homeMsgs', homeMsgs)\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":460,"y":120,"wires":[["2a25f279.c41bfe"]]},{"id":"405557b0.8bc198","type":"comment","z":"3aecfa59.984b76","name":"js","info":"/* jshint browser: true, esversion: 5 */\n/* globals document,Vue,window,uibuilder,VueECharts */\n// @ts-nocheck\n/*\n  Copyright (c) 2019 Julian Knight (Totally Information)\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n*/\n'use strict'\n\n/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */\n\n/** Reference the component (removes need for a build step with import) */\nVue.component('v-chart', VueECharts)\n\n\nvar date = [];\n\nvar data = [];\n\n// eslint-disable-next-line no-unused-vars\nvar app1 = new Vue({\n    el: '#app',\n    data: {\n        // Data for bar chart\n    ecOptionsBar: {\n      backgroundColor: '#000',\n      \n       grid: {\n        bottom: 100\n    },\n      \n      tooltip: {\n        trigger: 'axis',\n        position: function (pt) {\n            return [pt[0], '10%'];\n        }\n    },\n    title: {\n        left: 'center',\n        text: 'current power',\n    },\n    /*toolbox: {\n        feature: {\n            dataZoom: {\n                yAxisIndex: 'none'\n            },\n            restore: {},\n            saveAsImage: {}\n        }\n    },*/\n    \n    xAxis: {\n        type: 'category',\n        boundaryGap: false,\n        date: date ,\n         axisLabel: {\n            textStyle: {\n                color: '#fff'\n                    }\n        }\n    },\n    yAxis: {\n        type: 'value',\n        boundaryGap: [0, '100%'],\n          splitLine: {\n            lineStyle: {\n                color: '#404040'\n            }\n        },\n        axisLabel: {\n            textStyle: {\n                color: '#fff'\n                    }\n        }\n    },\n    dataZoom: [{\n        //type: 'inside',\n        show: true,\n        backgroundColor:'#fff',\n            fillerColor: \"rgba(255, 255, 255, 0.3)\",\n            dataBackground: {\n                 areaStyle: {\n                    color: \"#654062\"\n                        }\n                    },\n        start: 0,\n        end: 100\n    }, {\n        start: 0,\n        end: 10,\n        handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',\n        handleSize: '80%',\n        handleStyle: {\n            color: '#fff',\n            shadowBlur: 3,\n            shadowColor: 'rgba(0, 0, 0, 0.6)',\n            shadowOffsetX: 2,\n            shadowOffsetY: 2\n        }\n    }],\n    \n     \n    series: [\n        {\n            name: 'power',\n            type: 'line',\n            smooth: true,\n            symbol: 'none',\n            sampling: 'average',\n            itemStyle: {\n                color: 'rgb(255, 70, 131)'\n            },\n            areaStyle: {\n                color: new VueECharts.graphic.LinearGradient(0, 0, 0, 1, [{\n                    offset: 0,\n                    color: 'rgb(255, 158, 68)'\n                }, {\n                    offset: 1,\n                    color: 'rgb(255, 70, 131)'\n                }])\n            },\n            data: data\n        }\n    ]\n        },\n\n    }, // --- End of data --- //\n    computed: {\n    }, // --- End of computed --- //\n    methods: {\n    }, // --- End of methods --- //\n\n    // Available hooks: init,mounted,updated,destroyed\n    mounted: function(){\n        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3\n         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder\n         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.\n         * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names\n         */\n        uibuilder.start()\n\n        var vueApp = this\n\n        // Process new messages from Node-RED\n        uibuilder.onChange('msg', function (newVal) {\n            \n        \n        if (newVal.topic) {\n                let splitTopic = newVal.topic.split('/')\n                \n                if (splitTopic.length === 2) {\n                    \n                    if (splitTopic[0].toLowerCase() === 'test1') {\n                        \n                       \n                        \n                       for (var i=0; i<newVal.payload.length; i++ )\n                        {\n                           \n                        data.push(newVal.payload[i])\n                        }\n        \n                    }\n                    else\n                    {\n                       \n                   for (var j=0; j<newVal.payload.length; j++ )\n                        {\n                      \n                                var str = new Array(newVal.payload[j])\n                                str = str.toString()\n                                \n                                str = str.slice(0, -5)\n                                \n                                str = str.replace('T', '\\n')\n                                \n                                date.push(str)\n                        }\n                    }\n                }\n                \n        }\n        \n        \n        \n        \n        \n        //data.push(newVal.payload)\n        //date.push(newVal.payload[0])\n       \n      console.log(data)\n      console.log(date)\n        \n        })\n\n    } // --- End of mounted hook --- //\n\n}) // --- End of app1 --- //\n\n// EOF","x":140,"y":40,"wires":[]},{"id":"aced1d19.7171b","type":"inject","z":"3aecfa59.984b76","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\t    \"time\":\"2021-01-10T07:38:17.000Z\",\t    \"value\":1.30\t},\t{\t    \"time\":\"2021-01-10T07:38:07.000Z\",\t    \"value\":1.86\t}\t]","payloadType":"jsonata","x":110,"y":140,"wires":[["a730a2a1.03487","30d51a09.c50f26"]]}]

Georgi

Hi Georgi .. I recreated your flow as an example of what i meant that you could send the data as one msg instead of splitting it into two messages (x y axis seperate) that complicated things in the front-end.

Test Flow :

[{"id":"2a25f279.c41bfe","type":"uibuilder","z":"ac0f61dd.69e26","name":"","topic":"","url":"chartEcharts","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"useSecurity":false,"sessionLength":"","tokenAutoExtend":false,"x":750,"y":600,"wires":[[],["64b88bd9.a6fae4","7d9ce8bd.422a38"]]},{"id":"38b1eb76.194b14","type":"link in","z":"ac0f61dd.69e26","name":"echart-replay","links":["f1b1f460.632128"],"x":135,"y":660,"wires":[["b66e66c6.b06ea8"]]},{"id":"f1b1f460.632128","type":"link out","z":"ac0f61dd.69e26","name":"echart-controls","links":["38b1eb76.194b14"],"x":955,"y":660,"wires":[]},{"id":"aced1d19.7171b","type":"inject","z":"ac0f61dd.69e26","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":560,"wires":[["b66e66c6.b06ea8"]]},{"id":"f1604383.e5abf8","type":"debug","z":"ac0f61dd.69e26","name":"1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":370,"y":540,"wires":[]},{"id":"54a6a6f4.aba6c8","type":"function","z":"ac0f61dd.69e26","name":"restructure Data","func":"let arr = msg.payload\nlet yAxisData = []\nlet xAxisData = []\n\narr.forEach( el => {\n    \n    yAxisData.push(el.value);\n    xAxisData.push(el.time.replace(\"T\", \" \").slice(0,-5)); // and reformat time\n    \n})\n\nmsg.topic = \"chartData\"\n\nmsg.payload = { \n    yAxisData : yAxisData,\n    xAxisData : xAxisData\n}\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":460,"y":600,"wires":[["2a25f279.c41bfe","ecea6ad7.0531f"]]},{"id":"ecea6ad7.0531f","type":"debug","z":"ac0f61dd.69e26","name":"2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":590,"y":540,"wires":[]},{"id":"b66e66c6.b06ea8","type":"function","z":"ac0f61dd.69e26","name":"db data","func":"msg.payload = [\n{\n    \"time\":\"2021-01-08T04:38:17.000Z\",\n    \"value\":3.30\n},\n{\n    \"time\":\"2021-01-09T03:38:07.000Z\",\n    \"value\":1.86\n},\n{\n    \"time\":\"2021-01-10T06:38:17.000Z\",\n    \"value\":4.30\n},\n{\n    \"time\":\"2021-01-10T03:38:07.000Z\",\n    \"value\":1.86\n},\n{\n    \"time\":\"2021-01-10T05:48:17.000Z\",\n    \"value\":2.30\n},\n{\n    \"time\":\"2021-01-10T07:38:00.000Z\",\n    \"value\":1.86\n}\n]\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":300,"y":600,"wires":[["54a6a6f4.aba6c8","f1604383.e5abf8"]]},{"id":"64b88bd9.a6fae4","type":"debug","z":"ac0f61dd.69e26","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":910,"y":600,"wires":[]},{"id":"7d9ce8bd.422a38","type":"function","z":"ac0f61dd.69e26","name":"","func":"if (msg.uibuilderCtrl == \"ready for content\") {\n    \n    return {payload: \"replay\", topic: \"replay\"};\n}\nelse {\n    \n    return null\n    \n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":850,"y":660,"wires":[["f1b1f460.632128"]]}]

Index.html

<!doctype html><html lang="en"><head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

    <title>eCharts/VueJS Example - Node-RED UI Builder</title>
    <meta name="description" content="Node-RED UI Builder - eCharts/VueJS Example">

    <link rel="icon" href="./images/node-blue.ico">

    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />

    <link rel="stylesheet" href="./index.css" media="all">
</head><body>
    <div id="app">
        <b-container fluid id="app_container" class="p-3">

            <h1 class="ml-4 mt-4">
                Example of using <a href="https://github.com/ecomfe/vue-echarts?ref=madewithvuejs.com" target="_blank">Vue-ECharts</a>
                with uibuilder, VueJS and bootstrap-vue
            </h1>

            <b-row class="ml-4 mt-5">
                <b-card header="Bar Chart" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="center">
                        <v-chart :options="ecOptionsBar"/>
                </b-card>

            </b-row>

        </b-container>
    </div>

    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="../uibuilder/vendor/vue/dist/vue.min.js"></script>
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script>

    <!-- Loading from CDN, use uibuilder to install packages and change to vendor links -->
    <script src="https://cdn.jsdelivr.net/npm/echarts@4.1.0/dist/echarts.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-echarts@4.0.2"></script>

    <script src="./uibuilderfe.min.js"></script> <!--    //prod version -->
    <script src="./index.js"></script>

</body></html>

index.js

/* jshint browser: true, esversion: 5 */
/* globals document,Vue,window,uibuilder,VueECharts */
// @ts-nocheck
/*
  Copyright (c) 2019 Julian Knight (Totally Information)

  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.
*/
'use strict'

/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */

/** Reference the component (removes need for a build step with import) */
Vue.component('v-chart', VueECharts)


// var date = [];

// var data = [];

// eslint-disable-next-line no-unused-vars
var app1 = new Vue({
    el: '#app',
    data: {
        // Data for bar chart
    ecOptionsBar: {
      backgroundColor: '#000',
      
       grid: {
        bottom: 100
    },
      
      tooltip: {
        trigger: 'axis',
        position: function (pt) {
            return [pt[0], '10%'];
        }
    },
    title: {
        left: 'center',
        text: 'current power',
    },
    /*toolbox: {
        feature: {
            dataZoom: {
                yAxisIndex: 'none'
            },
            restore: {},
            saveAsImage: {}
        }
    },*/
    
    xAxis: {
        type: 'category',
        //type: 'time',
        boundaryGap: false,
        data: [],
         axisLabel: {
            textStyle: {
                color: '#fff'
                    }
        }
    },
    yAxis: {
        type: 'value',
        boundaryGap: [0, '100%'],
          splitLine: {
            lineStyle: {
                color: '#404040'
            }
        },
        axisLabel: {
            textStyle: {
                color: '#fff'
                    }
        }
    },
    dataZoom: [{
        //type: 'inside',
        show: true,
        backgroundColor:'#fff',
            fillerColor: "rgba(255, 255, 255, 0.3)",
            dataBackground: {
                 areaStyle: {
                    color: "#654062"
                        }
                    },
        start: 0,
        end: 100
    }, {
        start: 0,
        end: 10,
        handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
        handleSize: '80%',
        handleStyle: {
            color: '#fff',
            shadowBlur: 3,
            shadowColor: 'rgba(0, 0, 0, 0.6)',
            shadowOffsetX: 2,
            shadowOffsetY: 2
        }
    }],
    
     
    series: [
        {
            name: 'power',
            type: 'line',
            smooth: true,
            symbol: 'none',
            sampling: 'average',
            itemStyle: {
                color: 'rgb(255, 70, 131)'
            },
            areaStyle: {
                color: new VueECharts.graphic.LinearGradient(0, 0, 0, 1, [{
                    offset: 0,
                    color: 'rgb(255, 158, 68)'
                }, {
                    offset: 1,
                    color: 'rgb(255, 70, 131)'
                }])
            },
            data: []
        }
    ]
        },

    }, // --- End of data --- //
    computed: {
    }, // --- End of computed --- //
    methods: {
    }, // --- End of methods --- //

    // Available hooks: init,mounted,updated,destroyed
    mounted: function(){
        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder
         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
         * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names
         */
        uibuilder.start()

        var vueApp = this

        // Process new messages from Node-RED
        uibuilder.onChange('msg', function (newVal) {
            
            console.log("Msg received from Node-Red", newVal);
        
        if (newVal.topic) {


            vueApp.ecOptionsBar.series[0].data = newVal.payload.yAxisData
            vueApp.ecOptionsBar.xAxis.data = newVal.payload.xAxisData

            //  let splitTopic = newVal.topic.split('/')
                
                // if (splitTopic.length === 2) {
                    
                //     if (splitTopic[0].toLowerCase() === 'test1') {
                        
                       
                        
                //        for (var i=0; i<newVal.payload.length; i++ )
                //         {
                           
                //         data.push(newVal.payload[i])
                //         }
        
                //     }
                //     else
                //     {
                       
                //    for (var j=0; j<newVal.payload.length; j++ )
                //         {
                      
                //                 var str = new Array(newVal.payload[j])
                //                 str = str.toString()
                                
                //                 str = str.slice(0, -5)
                                
                //                 str = str.replace('T', '\n')
                                
                //                 date.push(str)
                //         }
                //     }
               // }
                
        }
        
        
        
        
        
        //data.push(newVal.payload)
        //date.push(newVal.payload[0])
       
    //   console.log(data)
    //   console.log(date)
        
        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

// EOF

It looks like your solution works! Thank you very much for your time!

You are welcome .. Very nice choice of chart with this zoom feature ...
i'll keep it mind for a future project :wink:

I continued working on the front-end code to make it a little more presentable.

  1. I centered the html elements and made the chart bigger and resizable with autoresize
<b-container fluid id="app_container" class="p-3">
        <b-row align-h="center">
          <h4 class="mt-4">
            Example of using
            <a href="https://github.com/ecomfe/vue-echarts?ref=madewithvuejs.com" target="_blank">Vue-ECharts</a>
            with uibuilder, VueJS and bootstrap-vue
          </h4>
        </b-row>

        <b-row align-h="center" class="mt-5">
          <b-card class="w-75" header="Bar Chart" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="center">
            <v-chart class="w-100" autoresize :options="ecOptionsBar" />
          </b-card>
        </b-row>
      </b-container>
  1. made the chart title white with some top padding
title: {
        left: "center",
        top: "20px",
        text: "Current Power",
        textStyle: { color: "white" },
      },

One discovery that I've made recently is that, if you want to simplify your HTML, you can wrap ALL of a Vue components properties in a single object variable:

<v-chart v-bind="mychartProps"></v-chart>

Thank you very much to both of you! I would like to ask you regarding the Echarts GEO/Map visualizations also. has anyone managed to use them successfully?

Not tried it I'm afraid.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.