Uibuilder include component

Hi everyone,

So I have been working further on my home dashboard with the uibuilder. But as the ‘default’ components not always do or look like what I have in mind I try ( or would like) to use another component available from npm.

However I can not get this to work properly.

As an example I looked at vue-gauge today. The nearest to a solution I got today was having the gauge display. But on every msg update. The gauge was redrawn next to the previous one , creating me a tremendous amount of gauges after a few minutes….

So my question is: can someone post me the steps to take to make this work and more generally: what are the steps to take to include ‘any’ (if they comply to regular standards that is?

Regards,
Pom

While it uses vue-svg-gauge rather than vue-gauge, have you seen TotallyInformation/uibuilder-vuejs-component-extras?

Unfortunately, I think - looking at the vue-gauge code, it hasn't been fully packaged. That means that you have to run some kind of build step to turn it from vue code to javascript.

If you look at hellocomet/vue-svg-gauge for example, it has a dist folder where there are versions that have already been built.

OMG not again….
Seems like I’m continuesly selecting crooked components :smiley:

So I tried to have a look at the "component-extras" but the installation of the front-end library keeps on failing....
Logs don't give much info:

7 Feb 09:23:54 - [info] [uibuilder:API:uibnpmmanage] Admin API. Running npm install for package uibuilder-vuejs-component-extras
7 Feb 09:23:57 - [warn] [uibuilder:API:uibnpmmanage] Admin API. ERROR Running npm install for package uibuilder-vuejs-component-extras
7 Feb 09:23:57 - [error] [uibuilder:API:uibnpmmanage] Admin API. npm command failed. npm install for package uibuilder-vuejs-component-extras

Sorry, my fault - I thought I'd published to npm but looks like I didn't. If you are using vNext, you can install from GitHub by prefixing the name with totallyinformation/ in the library manager. Or you can install manually from the command line if using v4.

It fails even on vNext anyway - though that gives you some proper information - looks like vue-loader has changed and the installation can't deal with it for some reason.


Try using vue-svg-gauge directly. You may note that it has instructions for using it via a script tag in your HTML. That is what all well packaged vue extensions should have.

So add <script src="../uibuilder/vendor/dist/vue-svg-gauge.min.js"></script> to your index.html file before the index.js script reference but after installing vue-svg-gauge via the library manager. As I remember it, using as a script tag, you don't need to add a reference to it in Vue.

The WIKI has a complete example.

Well I already had the vue-svg-gauge working last week, but it was missing some options I had in mind, but maybe I didn't explore entirely or correct.

For example: I would like to use a gauge for showing barometric pressure. My sensor reads from 300hPa to 1100hPa. But when I put these as min/max values the gauge becomes quirky. And no matter how I play with other settings I can't get the gauge to display "properly" (as in like with the default settings)

That's the reason I was looking into vue-gauge. I can make it work stand-alone but not included into the uibuilder... :frowning:

Can you share your code? Happy to take a quick look.

So I basically took the Integrating vue svg gauge howto from your Wiki page and adapted

HTML should show 3 gauges. 2 vue-svg-gauges and 1 vue-gauge.
On the left: svg gauge with default min/max settings. But I would like to have that between 800/1100 (which I try in the middle with a quirky result)
My sensor can measure down to 300hPa, bu if one day we go below 800hPa, we probably have other issues :smiley:
Also that gives for regular pressures that the needle should be moving "around" the middlepoint (as in the 3rd gauge) I also like the min/max labels from the 3rd gauge + 3 gauge colors (but I guess that also must be possible with the svg-gauge)

I have put chunk of html comment at the part where I'm stuck
(based my implementation of the vue-gauge on the "demo" included in the package)

<!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>vue-svg-gauge/VueJS Example - Node-RED UI Builder</title>
    <meta name="description" content="Node-RED UI Builder - vue-svg-gauge/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">
            <b-row>
                <b-card class="mr-1" header="vue svg gauge def min/max" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="center">
                    <vue-svg-gauge
                        :start-angle="-110"
                        :end-angle="110"
                        :value="gValue"
                        :separator-step="0"
                        :min="0"
                        :max="10"
                        :gauge-color="[{ offset: 0, color: '#347AB0'}, { offset: 100, color: '#8CDFAD'}]"
                        :scale-interval="0.1"
                    >
                        <div style="line-height: 12rem">{{rValue}}</div>
                    </vue-svg-gauge>
                </b-card>
                <b-card class="mr-1" header="vue svg gauge 300/1100" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="center">
                    <vue-svg-gauge
                        :start-angle="-110"
                        :end-angle="110"
                        :value="gValue"
                        :separator-step="0"
                        :min="800"
                        :max="1100"
                        :gauge-color="[{ offset: 0, color: '#347AB0'}, { offset: 100, color: '#8CDFAD'}]"
                        :scale-interval="0.1"
                    >
                        <div style="line-height: 12rem">{{rValue}}</div>
                    </vue-svg-gauge>
                </b-card>
                

                <b-card class="mr-1" header="vue gauge" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="center">
                    <div id="gaugeArea">
                    <!-- Below <vue-gauge> element line comes from the docs: have no idea how to implement this correctly (get an error of an undefined barometer) -->
                    <!-- Which is correct. The error goes away if in index.js I declare barometer, but the declaration must be invalid as nothing happens -->
                    <!-- If I put the below line in comment, the gauge is re-drawn + added upon each msg.payload update -->
                    <!-- Again which I understand why, but I have no clue on how to just have the needle updated -->
                        <!-- <vue-gauge :refid="barometer" :options="{'needleValue':85,'arcDelimiters':[85]}"></vue-gauge> -->
                    </div>
                </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>

    <script src="../uibuilder/vendor/vue-svg-gauge/dist/vue-svg-gauge.min.js"></script>
    <script src="../uibuilder/vendor/vue-gauge/assets/bundle.js"></script>

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

</body></html>

Here's the .js
the GaugeChart.gaugeChart(element...
continuesly redraws the gauge (which seems to be exactly what I ask the code to do) but I have no idea how to just have the value updated

/* jshint browser: true, esversion: 5 */
/* globals document,Vue,window,uibuilder */
// @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 */

// eslint-disable-next-line no-unused-vars
var app1 = new Vue({
    el: '#app',
    data: {
        gValue: 3,
        rValue: 3,
    }, // --- 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
        var value, value2 

        // Process new messages from Node-RED
        uibuilder.onChange('msg', function (newVal) {
            // We are assuming that msg.payload is an number between zero and 10

            value = (newVal.payload-300)/80
            value2 = (newVal.payload-800)/5
            // Change value
            vueApp.rValue = newVal.payload
            vueApp.gValue = value

            /* vue-chart */
            // Element inside which you want to see the chart
            let element = document.querySelector('#gaugeArea')
            // Properties of the gauge
            let gaugeOptions = {
                hasNeedle: true,
                needleColor: 'gray',
                needleUpdateSpeed: 1000,
                arcColors: ['rgb(44,151,222)','rgb(61,204,91)','rgb(239,214,19)','lightgray'],
                min: 0,
                max: 10,
                arcDelimiters: [40,60],
                rangeLabel: ['800', '1100'],
                centralLabel: newVal.payload,
                needleValue: value,
            }
            // Drawing and updating the vue-chart
            GaugeChart.gaugeChart(element, 300, gaugeOptions).updateNeedle(value2)
            //console.log(vueApp.gValue)
        })
    } // --- End of mounted hook --- //
}) // --- End of app1 --- //
// EOF

Much appreciate your input!

OK, I tried it and it clearly doesn't like the 800-1100 and indeed, it doesn't seem to like all sorts of different min/max, especially with angles.

Oh, and I've just checked the issues - no responses - even for the one I raised in 2020 - so it looks like this is a bust too.


Have you tried:

RadialBar / Circular Gauge Chart Guide & Documentation – ApexCharts.js

Or this one seems to work well with no messing:

vgauge - npm (npmjs.com)

You can install it with uibuilder and use the reference: <script src="../uibuilder/vendor/vgauge/dist/VGauge.min.js"></script> - again, no need to add as a component, just use directly:

            <v-gauge 
                :value="gValue"
                width="500px"
                unit="mPa"
                minValue="800"
                maxValue="1100"
            ></v-gauge>

Lots of options available from the underlying gauge.js library.

1 Like

hi @TotallyInformation ,

Thx for the tip on the vgauge, got it working but not entirely as you posted (no offence intended)
in HTML at the location where you like the gauge:

                            <v-gauge 
                                :options="barometerOptions"
                                width="350"   
                                :min-value="minValue"
                                :max-value="maxValue"
                                unit= "hPa"                                
                                :value="gValue"
                            >
                            </v-gauge>

mind the min-value and max-value. This was a tip I saw in the console when copy/pasting your snippet:
[Vue tip]: Prop "minvalue" is passed to component <Anonymous>, but the declared prop name is "minValue". Note that HTML attributes are case-insensitive and camelCased props need to use their kebab-case equivalents when using in-DOM templates. You should probably use "min-value" instead of "minValue".
changing your snippet to min-value gave me this in console:
[Vue warn]: Invalid prop: type check failed for prop "minValue". Expected Number with value 800, got String with value "800".
So I put a : in front an referenced it in my index.js:

var vueTST = new Vue({
    el: '#vueTST',
    data: {
        batStatus: 0,
        version: '1.0',
        gValue: 0,
        minValue: 800,
        maxValue: 1100,
        barometerOptions: barometerOpts,
    },

As I have no other clue on how I could pass an 'integer' in HTML.

What I also added in HTML is this:

        <script src="../uibuilder/vendor/vgauge/dist/VGauge.min.js"></script>
        
        <!-- local JS -->
        <script src="./barometer.js"></script>
        <script type="module" src="./index.js"></script> <!-- Was already there just for reference where I added it -->

And in barometer.js I have:

var barometerOpts = {
    angle: 0, // The span of the gauge arc
    lineWidth: 0.2, // The line thickness
    radiusScale: 1, // Relative radius
    pointer: {
      length: 0.6, // // Relative to gauge radius
      strokeWidth: 0.035, // The thickness
      color: '#000000' // Fill color
    },
    limitMax: true,     // If false, max value increases automatically if value > maxValue
    limitMin: true,     // If true, the min value of the gauge will be fixed
    colorStart: '#6F6EA0',   // Colors
    colorStop: '#C0C0DB',    // just experiment with them
    strokeColor: '#EEEEEE',  // to see which ones work best for you
    generateGradient: true,
    highDpiSupport: true,     // High resolution support
    // renderTicks is Optional
    renderTicks: {
      divisions: 5,
      divWidth: 1,
      divLength: 0.7,
      divColor: '#333333',
      subDivisions: 10,
      subLength: 0.5,
      subWidth: 0.6,
      subColor: '#666666'
    },
    staticLabels: {
        font: "10px sans-serif",  // Specifies font
        labels: [800, 850, 900, 950, 1000, 1100],  // Print labels at these values
        color: "#000000",  // Optional: Label text color
        fractionDigits: 0  // Optional: Numerical precision. 0=round off.
      },
    staticZones: [
        {strokeStyle: "#F03E3E", min: 800, max: 900}, // Red
        {strokeStyle: "#30B32D", min: 900, max: 1000}, // Green
        {strokeStyle: "#FFDD00", min: 1000, max: 1100}, // Yellow
    ],
};

And further in the index.js the classic way to go to act upon msg changes:

        uibuilder.onChange('msg', function(msg){
            console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            switch(msg.topic){
                     *snip* // I have more topics I subscribed to so I have cut them out for readability
                case "weerstations/home/sensors/pressure" :
                    console.info('[indexjs:uibuilder.onChange] pressure received from Node-RED server:', msg)
                    vueTST.gValue = Number(msg.payload) // set gauge value (change payload to number)
                    break;

In the end this renders to this:
image
Which is already pretty close to what I'm seeking to get as a result (I guess i just need to tweak a bit with the values left and right)

Many thx again for your patience and time!

regards,
Pom

[Vue warn]: Invalid prop: type check failed for prop "minValue". Expected Number with value 800, got String with value "800".

From what i read this happens because when you bind a value, html elements always return a string

Instead of using the shorthand syntax :
try v-model.number="minValue"

(source)

To have this?

min-value=v-model.number="800"

None taken - I have to give you something to make sure you are paying attention :grinning:

You really don't need that given that it just contains a single object. Use index.js and save an extra server call. You can put anything in index.js. You could either put that object as a Vue variable (in case you want to use it directly in your html or as a separate javascript variable outside the Vue app code.

Really, that is a slightly poorly worded message. You could have put :min-value="800" which should also have worked. The reason that should work is that the leading : is a shorthand for a Vue attribute that turns the passed value into a JavaScript expression. To pass a string in the same way, you would have needed :min-value="'800'" (noting the extra single quotes there to make the number into a string).

Still, it certainly doesn't hurt to have the min/max as something you can change.

Phew, glad you got there. And you've seen the worst problems that Vue and similar frameworks are going to throw up.

Pleasure, at least I now know that my example vue extensions are no longer valid and we have a new contender for best Vue gauge. :slight_smile:

1 Like

:saluting_face:

I think I did that initially but it complained about this line (Can't recall the exact error but it was something like a referenceError) :

:options="barometerOptions"

I'll try that again. There were maybe other things I did wrong at that point.
Good remark about the server call.

confirmed

Both the joy and horror of platforms where everyone develops at some point-in-time a piece of code. Developing is often not the issue, but the maintenance can become very time intensive. Again hats off to you if I see how much you invest :+1:

sorry .. bad suggestion .. need to read more about it
best to use the hard coded value as Julian showed and you tested that works

interesting to know if a vue prop can be passed dynamically and also be a number ? :face_with_monocle:

I gave that another go and it definitely works. However, I think I did spot a few oddities in how the underlying library was consuming the data.

But I tried this:

    data: {

        // Gauge value
        gValue: 0,
        
        // Max/Min gauge values
        gMax: 100,
        gMin: 0,

        // Width of gauge
        gWidth: '500px',
        
        // gauge.js options
        gOpts: {
            angle: -0.2, // The span of the gauge arc - 0=180deg, -0.2 for 240deg
            limitMax: false,
            limitMin: false,
            generateGradient: true,
            highDpiSupport: true,
            percentColors: [[0, "#a9d70b" ], [.50, "#f9c802"], [1.00, "#ff0000"]],
            strokeColor: '#E0E0E0',
            staticLabels: {
                //font: "10px sans-serif",  // Specifies font
                labels: [10, 20, 30, 40, 50, 60, 70, 80, 90],  // Print labels at these values
                //color: "#000000",  // Optional: Label text color
                //fractionDigits: 0  // Optional: Numerical precision. 0=round off.
            },
            renderTicks: {
                divisions: 5,
                divWidth: 1.1,
                divLength: 0.7,
                divColor: '#333333',
                subDivisions: 3,
                subLength: 0.5,
                subWidth: 0.6,
                subColor: '#666666',
            }
        },

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

with this:

    <div id="app">
        <div style="">
            <v-gauge 
                :value="gValue"
                :width="gWidth"
                :min-value="gMax"
                :max-value="100"
                :options="gOpts"
                unit="%"
            ></v-gauge>
        </div>
        <div>
            Value: {{gValue}}
        </div>
    </div>

image

How very true.

Which led me to think about recommendations again and I started (but didn't have much time for) compiling a list of potentially more useful options.

Two possibilities stood out so far:

  • vue-chartkick which supports chart.js, Google charts and Highcharts
  • vue-chartjs which is just chart.js of course

Both of those libraries can be used without a build step just by including the scripts as with vgauge. Of course, they support a lot more than just gauges.

Along the way I already spotted a couple of other charting or graph tools that have been abandoned, some of which are quite popular. So sticking with a library that has lots of support seems sensible.

1 Like

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