Vue JS chart is only updating after resizing window

Hi guys,
I have a strange issue which i can't really explain and i need your help in solving it. I have a live chart where im updating the series through an uibuilder block. It works but only if i resize my window. It's as if resizing the window triggers rendering. what could i be missing? Any help/hint is highly appreciated, thanks!

Below is my setup

[{"id":"b18a50dd.f7e5c","type":"uibuilder","z":"f6b290d236523026","name":"","topic":"","url":"chart","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"blank","extTemplate":"","showfolder":false,"reload":false,"sourceFolder":"src","deployedVersion":"5.1.1","x":600,"y":260,"wires":[["8fb723c8a4461824"],["5917eaf1b86cc79a"]]},{"id":"ac92bf4d2b4c46cd","type":"inject","z":"f6b290d236523026","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"battery\":100,\"humidity\":$floor($random() * 100),\"last_seen\":$millis(),\"linkquality\":54,\"power_outage_count\":20411,\"pressure\":980,\"temperature\":24.29,\"voltage\":3035}","payloadType":"jsonata","x":390,"y":260,"wires":[["b18a50dd.f7e5c"]]},{"id":"8fb723c8a4461824","type":"debug","z":"f6b290d236523026","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":800,"y":200,"wires":[]},{"id":"5917eaf1b86cc79a","type":"debug","z":"f6b290d236523026","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":800,"y":320,"wires":[]}]

This is my js script:

/* jshint browser: true, esversion: 5 */
/* globals document,Vue,window,uibuilder,VueApexCharts */
// @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 apexchart component (removes need for a build step) */
Vue.component('apexchart', VueApexCharts)

// eslint-disable-next-line no-unused-vars
var app1 = new Vue({
    el: '#app',
    components: {
        apexchart: VueApexCharts,
    },
    data: {

        series: [{
            name: '',
            data: []
        },
        {
            name: '',
            data: []
        }],
        chartOptions: {
            chart: {
                type: 'area',
                stacked: false,
                height: 350,
                zoom: {
                    type: 'x',
                    enabled: true,
                },
                toolbar: {
                    autoSelected: 'zoom'
                }
            },
            dataLabels: {
                enabled: false
            },
            markers: {
                size: 0,
            },
            stroke: {
                width:[1,1,4]
            },
            title: {
                text: 'Temperature & Humidity',
                align: 'left',
                offsetX: 110
            },
            yaxis: [
              {
                axisTicks: {
                  show: true,
                },
                axisBorder: {
                  show: true,
                  color: '#008FFB'
                },
                labels: {
                  style: {
                    colors: '#008FFB',
                  }
                },
                title: {
                  text: "Temperature in °C ",
                  style: {
                    color: '#008FFB',
                  }
                },
                tooltip: {
                  enabled: true
                }
              },
              {
                seriesName: 'Humidity',
                opposite: true,
                axisTicks: {
                  show: true,
                },
                axisBorder: {
                  show: true,
                  color: '#00E396'
                },
                labels: {
                  style: {
                    colors: '#00E396',
                  }
                },
                title: {
                  text: "Humidity in %",
                  style: {
                    color: '#00E396',
                  }
                },
              }
            ],
            tooltip: {
              fixed: {
                enabled: true,
                position: 'topLeft', // topRight, topLeft, bottomRight, bottomLeft
                offsetY: 30,
                offsetX: 60
              },
            },
            legend: {
              horizontalAlign: 'left',
              offsetX: 40
            }
        },


    }, // --- 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(msg) {
            console.log("Msg received from NR", msg.payload)

            if (typeof msg.payload.temperature != "undefined")
            {

                var temp = {
                    'x' : new Date(msg.payload.last_seen).toISOString(),
                    'y' : msg.payload.temperature
                }

                let newData = vueApp.series[0].data;

                newData.push(temp);

                if (newData > 3000)
                {
                    newData.shift();
                }

                vueApp.series[0] = {
                    data : newData,
                    name : 'Temperature'
                };
            }

            if (typeof msg.payload.humidity != "undefined")
            {
                var temp = {
                    'x' : new Date(msg.payload.last_seen).toISOString(),
                    'y' : msg.payload.humidity
                }

                let newData = vueApp.series[1].data;

                newData.push(temp);

                if (newData > 3000)
                {
                    newData.shift();
                }

                vueApp.series[1] = {
                    data : newData,
                    name : 'Humidity'
                };
            }
        })
    } // --- End of mounted hook --- //

})
// EOF
<!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>ApexChart/VueJS Example - Node-RED UI Builder</title>
    <meta name="description" content="Node-RED UI Builder - ApexChart/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 id="app_container">

            <h1>
                Examples of using <a href="https://apexcharts.com/docs/vue-charts/" target="_blank">ApexChart</a>
                with uibuilder, VueJS and bootstrap-vue
            </h1>

            <b-row>
                <b-card col class="w-50" header="Time series Example" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="center">
                        <apexchart type="area" height="350" :options="chartOptions" :series="series" ></apexchart>
                </b-card>
            </b-row>

        </b-container>
    </div>

    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->
    <script src="../uibuilder/vendor/socket.io/socket.io.js">/* REQUIRED: Socket.IO is loaded only once for all instances. Without this, you don't get a websocket connection */</script>
    <!-- Vendor Libraries - Load in the right order, use minified, production versions for speed -->
    <script src="../uibuilder/vendor/vue/dist/vue.min.js">/* prod version with component compiler */</script>
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.js">/* dev version with component compiler */</script> -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.runtime.min.js">/* prod version without component compiler */</script> -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.min.js">/* remove 'min.' to use dev version */</script> <!--  -->

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

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

</body></html>

Here is an illustration:
image

after resizing window:
image

the answer is out there (checkout the 1st stackoverflow thread)

Thanks, i've have tried both soutions from stackoverlow

None of the worked :frowning:

Can you share some example input data please? Also, please share what version of node.js, node-red and uibuilder you are using. Also the browser you are using. Are there any errors showing in the browser dev console?

In fact, don't bother as I've found the issue. It is a VueJS issue. In the example code from the WIKI, the only chart of the 2 that is updated is the Pie chart. As it happens, the ApexChart data structure for Pie charts is simpler than that of a bar or line chart. Because of that, when you update the VueJS variable that defines the chart values, Vue is able to know what to do, it updates the variable which triggers Apex to redraw.

However, if you try to directly update the more complex data structure for a line chart, VueJS does not know what to do and so it does not trigger an update.

To get round that, you could force VueJS to trigger an update when you change the data - see the VueJS docs on how to do that. Or you can use the example given on the ApexCharts website. Namely have a separate Vue variable for your data array and then replace the whole dseries variable after an update - this will trigger the redraw.

The reason is explained in the docs here: Reactivity in Depth — Vue.js (vuejs.org)

This works:

        uibuilder.onChange('msg', function (newVal) {
            if ( typeof newVal.payload === 'number' ){
                const data = vueApp.dseries[0].data
                data.push(newVal.payload)
                data.shift()
                vueApp.dseries = [{ data: data }]
            }
        })

I've added an update to the WIKI page explaining this.

Sorry i never got time for this. Now i have some more time. my code is very similar to yours but doesn't seem to work is there something that i overlooked?:

 // 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(msg) {
            console.log("Msg received from NR", msg.payload)

            if (typeof msg.payload.temperature != "undefined")
            {

                var temp = {
                    'x' : new Date(msg.payload.last_seen).toISOString(),
                    'y' : msg.payload.temperature
                }

                const newDataTemp= vueApp.series[0].data;

                newDataTemp.push(temp);

                if (newDataTemp > 3000)
                {
                    newDataTemp.shift();
                }
            }

            if (typeof msg.payload.humidity != "undefined")
            {
                var temp = {
                    'x' : new Date(msg.payload.last_seen).toISOString(),
                    'y' : msg.payload.humidity
                }

                const newDataHum = vueApp.series[1].data;

                newDataHum.push(temp);

                if (newDataHum > 3000)
                {
                    newDataHum.shift();
                }
            }

            vueApp.series = [{
                    data : newDataTemp,
                    name : 'Temperature'
                },
                {
                    data : newDataHum,
                    name : 'Humidity'
                }]
        })
    } // --- End of mounted hook --- //```

@TotallyInformation I got it somehow working with Vue.set() function, though i find my code quite ugly (im just a C programmer). is there an elegant way to do this? Thanks alot for your help

    // 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(msg) {
            console.log("Msg received from NR", msg.payload)
            var newIndex = 0;
            const newDataTemp= vueApp.series[0].data;
            const newDataHum = vueApp.series[1].data;

            if (typeof msg.payload.temperature != "undefined")
            {

                var temp = {
                    'x' : new Date(msg.payload.last_seen).toISOString(),
                    'y' : msg.payload.temperature
                }



                newDataTemp.push(temp);

                if (newDataTemp > 3000)
                {
                    newDataTemp.shift();
                }
            }

            if (typeof msg.payload.humidity != "undefined")
            {
                var temp = {
                    'x' : new Date(msg.payload.last_seen).toISOString(),
                    'y' : msg.payload.humidity
                }

                newDataHum.push(temp);

                if (newDataHum > 3000)
                {
                    newDataHum.shift();
                }
            }
            const tempContainer = {};
            const humContainer = {};


            tempContainer.name = "Temperature";
            tempContainer.data = newDataTemp.slice();

            Vue.set(vueApp.series, 0, tempContainer);

            humContainer.name = "Humidity";
            humContainer.data = newDataHum.slice();

            Vue.set(vueApp.series, 1, humContainer);
        })
    } // --- End of mounted hook --- //

Best regards

I'm afraid that is probably about the best way - limitations of VueJS and the ApexChart data structures I'm afraid. Not sure if Vue v3 is any better as I've not played with it extensively.

Alright thanks!

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