Skip to content

Integrating ApexCharts with VueJS

Julian Knight edited this page Dec 21, 2022 · 5 revisions

UPDATE 2022-12-21 - Updated the code to use the latest uibuilder v6 FE library. Also added a Line chart with expanding data and updated both the Line and Bar chart to update correctly. Noting that ApexChart uses far too complex a data object in many cases and this does not play nicely with VueJS. This means having to jump through hoops to get the chart to update dynamically. Also, while the ApexChart examples on their website look comprehensive - they have far too many missing pieces that make learning much too hard for beginners.

UPDATE 2021-03-01 - I've had reports that ApexCharts doesn't work with Android Chrome or Android Firefox. But I've no idea how true that is.


Here is an example of how to use ApexCharts with the default uibuilder v2 installation to add a simple chart.

Add an instance of uibuilder to your flows and replace the index.html and index.js files with the code below.

I'm loading the chart libraries using a CDN here, the next step would be to install the two packages using uibuilder's npm interface.

The example flow sends a random number to the app every 5 seconds which is used to update the charts.

flow

[
    {
        "id": "6814d51fcda06a50",
        "type": "inject",
        "z": "2712d438cfda4c65",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "5",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "($random()*10)+($random()*10)",
        "payloadType": "jsonata",
        "x": 140,
        "y": 80,
        "wires": [
            [
                "f12c790f580f771b"
            ]
        ]
    },
    {
        "id": "f12c790f580f771b",
        "type": "uibuilder",
        "z": "2712d438cfda4c65",
        "name": "",
        "topic": "",
        "url": "apex-charts",
        "fwdInMessages": false,
        "allowScripts": false,
        "allowStyles": false,
        "copyIndex": true,
        "templateFolder": "vue",
        "extTemplate": "",
        "showfolder": false,
        "reload": false,
        "sourceFolder": "src",
        "deployedVersion": "6.1.0",
        "showMsgUib": false,
        "x": 330,
        "y": 80,
        "wires": [
            [
                "f0d9177615199688"
            ],
            [
                "a98558a0e03506ef"
            ]
        ]
    },
    {
        "id": "f0d9177615199688",
        "type": "debug",
        "z": "2712d438cfda4c65",
        "name": "debug 34",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "counter",
        "x": 495,
        "y": 60,
        "wires": [],
        "l": false
    },
    {
        "id": "a98558a0e03506ef",
        "type": "debug",
        "z": "2712d438cfda4c65",
        "name": "debug 35",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "counter",
        "x": 495,
        "y": 120,
        "wires": [],
        "l": false
    }
]

html

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>ApexCharts/VueJS/bootstrap-vue Example - Node-RED uibuilder</title>
    <meta name="description" content="Node-RED uibuilder - ApexCharts/VueJS/bootstrap-vue 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" />
    <!-- Your own CSS -->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

</head><body>

    <div id="app" class="uib" v-cloak><!-- All UI code needs to be in here -->
        <b-container id="app_container"><!-- Wraps the bootstrap-vue formatting -->

            <b-img src="./images/node-blue-192x192.png" rounded left v-bind="imgProps" alt="Blue Node-RED" class="mt-1 mr-2"></b-img>
            <h1>ApexCharts/VueJS/bootstrap-vue Example</h1>
            <p>
                Examples of using <a href="https://apexcharts.com/docs/vue-charts/" target="_blank">ApexChart</a>
                with uibuilder, VueJS and bootstrap-vue.
            </p>
            <p>
                Note that ApexCharts uses very complex data objects that VueJS can struggle with.
                See the notes in <code>index.js</code>
            </p>
            
            <b-row>
                <b-card col class="w-50" header="Bar Chart" border-variant="primary" header-bg-variant="primary"
                    header-text-variant="white" align="center">
                    <apexchart width="100%" type="bar" :options="boptions" :series="bseries"></apexchart>
                </b-card>
            
                <b-card col class="w-50" header="Donut Chart" border-variant="primary" header-bg-variant="primary"
                    header-text-variant="white" align="center">
                    <apexchart width="100%" type="donut" :options="doptions" :series="dseries"></apexchart>
                </b-card>

                <b-card col class="w-100" header="Line Chart" border-variant="primary" header-bg-variant="primary"
                    header-text-variant="white" align="center">
                    <apexchart width="100%" type="line" :options="loptions" :series="lseries"></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/vue/dist/vue.js">/* prod version with 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="./uibuilder.iife.min.js">/* REQUIRED: remove 'min.' to use dev version */</script>
    <script src="./index.js">/* OPTIONAL: Put your custom code in that */</script>
    <!-- #endregion -->

</body></html>

JavaScript

/* globals Vue, uibuilder */
// @ts-nocheck
'use strict'

/** Reference the apexchart component (removes need for a build step) */
Vue.component('apexchart', VueApexCharts)

const ldata = [30, 40, 45, 50, 49, 60, 70, 91]
const laxis = [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998]

const app = new Vue({
    el: '#app',

    data() { return {

        startMsg    : 'Vue has started, waiting for messages',
        imgProps    : { width: 75, height: 75 },

        //#region ---- Data for bar chart ----
        boptions: {
            chart: {
                id: 'vuechart-bar-example'
            },
            xaxis: {
                categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998],
            },
        },
        bseries: [{
            name: 'series-1',
            data: [30, 40, 45, 50, 49, 60, 70, 91],
        }],
        //#endregion ---- ---- ----

        //#region ---- Data for donut chart ----
        doptions: {
            chart: {
                id: 'vuechart-donut-example'
            },
        },
        dseries: [44, 55, 41, 17, 15],
        //#endregion ---- ---- ----

        //#region ---- Data for line chart ----
        loptions: {
            chart: {
                id: 'vuechart-line-example'
            },
            xaxis: {
                categories: laxis, // see updLchart for why we use an external var here
            },
        },
        lseries: [{
            name: 'series-1',
            data: ldata,
        }],
        //#endregion ---- ---- ----


    }}, // --- End of data --- //

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

    methods: {
        // Update the bar chart from msg.payload
        // Bar charts have complex data so have to replace the whole data series
        // in order for VueJS to recognise it has changed.
        updBchart(msg) {
            // Replace random element with new value
            const bdata = this.bseries[0].data
            bdata[Math.floor(Math.random()*this.boptions.xaxis.categories.length)] = Math.floor(msg.payload)
            // Yes, we have to replace the whole data series - the outer-most property MUST be updated directly
            this.bseries = [ { data: bdata } ]

            // If adding to this chart, you need to replace this.boptions not this.boptions.xaxis
        },

        // Update the Donut chart from msg.payload
        // Pie/Donut charts have simple data so only need simple updates
        updDchart(msg) {
            // Add new element
            this.dseries.push(msg.payload)
            // Lose the first element
            this.dseries.shift()
        },

        // Update the line chart from msg.payload
        // Line charts have complex data so have to replace the whole data series
        // in order for VueJS to recognise it has changed.
        updLchart(msg) {
            // Add new point with new value
            // const ldata = this.lseries[0].data
            // ldata.push( Math.floor(msg.payload) )
            ldata.push( Math.floor(msg.payload) )
            // Yes, we have to replace the whole data series - the outer-most property MUST be updated directly
            this.lseries = [ { data: ldata } ]

            // Add matching x-axis entry
            // WARNING: Don't try to grab the xaxis direct from the loptions - for some reason, this results
            //          in the data being lost. A bug in Vue or ApexCharts, not sure which.
            laxis.push( ++laxis[laxis.length-1] )
            this.loptions = { xaxis: { categories: laxis } }

            // Slice an entry off the front for next time around if >100 entries to keep things reasonable
            if (laxis.length >= 100 ) {
                ldata.shift()
                laxis.shift()
            }
        },
    }, // --- End of methods --- //

    mounted: function(){

        const app = this  // Reference to `this` in case we need it for more complex functions

        // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO
        uibuilder.onChange('msg', (msg)=> {
            if (typeof msg.payload === 'number') {
                app.updBchart(msg)

                app.updDchart(msg)

                app.updLchart(msg)
            }
        })

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

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

// EOF
Clone this wiki locally