Access the msg.payload in ui_builder using plotly

I am trying to custom my user interface using ui_builder instead of the node-red dashboard to make something more personal... Then I have tried to use the plotly library to plot my data in real time.

I have my html code which run perfectly in a normal webpage, but now I'd like to use the msg.payload (for the example I have created a msg.payload with a random value) inside my ui_builder using plotly.
I have tried to use scope.$watch to access the msg.payload as I have done it in a template node but it looks to not working.
I thing I am using the wrong way to access the msg.payload inside ui_builder... how can I access this msg.payload inside my javascript function in ui_builder... Thanks

Here is my node for example :

[{"id":"24aaa183.d3b73e","type":"tab","label":"Flow 7","disabled":false,"info":""},{"id":"aedd60ec.f61b9","type":"uibuilder","z":"24aaa183.d3b73e","name":"","topic":"","url":"uibuilder","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":1340,"y":540,"wires":[[],[]]},{"id":"38283b65.8755f4","type":"function","z":"24aaa183.d3b73e","name":"","func":"msg.payload = Math.random();\nreturn msg;","outputs":1,"noerr":0,"x":960,"y":500,"wires":[["aedd60ec.f61b9","6ad592da.7f6d4c"]]},{"id":"7d672bd7.a70f24","type":"inject","z":"24aaa183.d3b73e","name":"","topic":"","payload":"","payloadType":"date","repeat":"0.1","crontab":"","once":false,"onceDelay":0.1,"x":670,"y":460,"wires":[["38283b65.8755f4"]]},{"id":"6ad592da.7f6d4c","type":"debug","z":"24aaa183.d3b73e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1340,"y":380,"wires":[]}]

Thank you for your help.

Which framework are you using to receive data ?

OK, so you are thinking in Angular terms whereas the default framework for uibuilder is VueJS. The concepts are similar but VueJS is much easier to use.

What we need to understand now though is what your html and js front-end code looks like so if you can share those we will be able to help further.

Assuming that you want to use VueJS. All you need to do is add a variable to the app1.data object and then in the uibuilder.onchange function for the incoming msg, assign the payload to the variable. Add that variable to the html using something like {{myvarname}} to display it, it will update automatically.

Note though that you are sending data very rapidly. While Node-RED, uibuilder and Vue will happily cope with that, your users might not :grinning:

1 Like

Thank you for this answer ! I thought my code whould be in the flow I have copy in my post...

Now I get that I can have my msg.payload in my html code using {{ }} but in my case I need to pass my msg.payload into my javascript code... How can I do it ?
Thank you

Here it is :

Real-Time Chart with Plotly.js
<div class="wrapper">
    <div id="chart"></div>
    <script>

        (function(scope) {
            scope.$watch('msg', function(msg) {
                getData(msg.payload);
        });

        function getData(d) {
            return d;
        }  
        Plotly.plot('chart',[{
            y:[getData()],
            type:'line'
        }]);

        var cnt = 0;
        setInterval(function(){
            Plotly.extendTraces('chart',{ y:[[getData()]]}, [0]);
            cnt++;
            if(cnt > 500) {
                Plotly.relayout('chart',{
                    xaxis: {
                        range: [cnt-500,cnt]
                    }
                });
            }
        },15);
        })(scope);
    </script>
</div>
</body>

So are you using AngularJS or VueJS - it makes a big difference.

With VueJS, as I said, your data is in a variable in app1.data if you are following the default template. That exists in index.js. You wouldn't tend to put JavaScript in your index.html file.

The code you have shared looks a lot more like code you would put into the Dashboard's template node. uibuilder isn't like that at all.

If you share the content of your index.html and index.js files, we can move forward.

Actually, even if the style is not good, this is the kind of things I'd like to do.
On the top with a full width, a live plot of my data (which will be my msg.payload of a cell gauge).
Then below, I will have the instruction which the user should do.

Here is a code that I have start to do. I am using "normal" javascript...

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
* {
  box-sizing: border-box;
}

/* Create two equal columns that floats next to each other */
.column {
  float: left;
  position: relative;
  width: 50%;
  height: 500px; /* Should be removed. Only for demonstration */
}

/* Clear floats after the columns */
.row:after {
  content: "";
  display: table;
  clear: both;
}

#myProgress {
  width: 100%;
  background-color: #ddd;
    position: absolute;
    left: 0px;
    height: 100%;
    top: 0px;
}

#myBar {
  width: 100%;
  height: 100%;
  background-color: #808080;

}

.arr{
position:absolute;
}
.avv{
position:absolute;
height:100%;
width: 100%;
text-align: center;

}


#photo{
  display: flex;
  display: block;
  background-size:cover;
  background-repeat:no-repeat;
  max-width:250px;
  max-height:250px;

  width: auto;
  height: auto;

  

}

</style>
</head>
<body>

      <div class="navbar"><span>Real-Time Chart with Plotly.js</span></div>
    <div class="wrapper">
    <div id="chart"></div>

<h2>Two Equal Columns</h2>

<div class="row">
  <div class="column" style="background-color:#aaa;">
    <div id="myProgress">
  <div class= "arr" id="myBar"></div>
  <div class="avv"> <h2 id="texte"></h2></div>

</div>
  </div>
  <div class="column" style="background-color:#bbb;">
  <img id="photo" src="http://localhost:1880/handgripSurTable.JPG">

  </div>
</div>

<br>
<button onclick="move()">Click Me</button> 

<script>

function move() {
    var i = 0;
    var nbEtape = 21;
    var decompte1 = 3;
    var decompte2 = 8;
    var decompte3 = 13;
    var decompte4 = 18;
    var elem = document.getElementById("myBar");
    var photo = document.getElementById("photo");
    var texte = document.getElementById("texte");
    var width = 100;
    var message = [
    "Laisser l'appareil sur la table",
    "Prenez l'appareil dans la main droite et positionnez votre bras le long de votre corps comme sur la photo", 
    "Attention, préparez-vous à forcer", 
    "",
    "Forcez",
    "Reposez l'appareil sur la table, vous avez 30 secondes de récupération",
    "Reprenez l'appareil dans la main droite, et placez votre bras le long du corps comme précédemment (comme sur la photo)",
    "Attention, préparez-vous à forcez",
    "",
    "Forcez",
    "Reposez l'appareil sur la table, vous allez maintenant passer au test pour votre main gauche",
    "Prenez l'appareil dans la main gauche et positionnez votre bras le long de votre corps comme sur la photo",
     "Attention, préparez-vous à forcer", 
    "",
    "Forcez",
    "Reposez l'appareil sur la table, vous avez 30 secondes de récupération",
    "Reprenez l'appareil dans la main gauche, et placez votre bras le long du corps comme précédemment (comme sur la photo)",
    "Attention, préparez-vous à forcez",
    "",
    "Forcez",
    "Le test est terminé, vous pouvez reposer l'appareil sur la table et passez à l'atelier suivant"
    ];
    var image = [
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    ]
    var interval = [20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20];

    var messageMainDroite = "";
    var id = setInterval(frame, interval[i] );

      function frame() {
          width = width - 1;
          elem.style.width = width + "%";
          if (i == decompte1 || i == decompte2 || i == decompte3 || i == decompte4) {
            if (width > 66) {
            texte.textContent = '3';
            }
            if (width < 66 && width > 33) {
            texte.textContent = '2';
            }
            if (width < 33 && width > 0) {
            texte.textContent = '1';
            } 
          } else {
            texte.textContent = message[i];
            photo.setAttribute("src",image[i]);
          }

          if (width == 0 && i < nbEtape) {
            width = 100;
            i++;
            clearInterval(id);
            id = setInterval(frame, interval[i]);
          }
          if (i == nbEtape) {
            clearInterval(id);
          }
        }
    
    
      
   
  } // fin de move()

              function getData() {
                return Math.random();
            }  
            Plotly.plot('chart',[{
                y:[getData()],
                type:'line'
            }]);
            
            var cnt = 0;
            setInterval(function(){
                Plotly.extendTraces('chart',{ y:[[getData()]]}, [0]);
                cnt++;
                if(cnt > 500) {
                    Plotly.relayout('chart',{
                        xaxis: {
                            range: [cnt-500,cnt]
                        }
                    });
                }
            },15);
</script>

</body>

Maybe I should put this code in a dashboard's template ?
But in dashboard template, I can not completly do what I want we the style of my page... since I have vertical "group" ..

That's fine though as your code gets more complex, it is better to have it separate. VueJS and other newer frameworks also support "components" that you might want to investigate.

The code you've provided is NOT uibuilder code and could not work.

You have removed all of the required libraries. Please go back to the default template and read the comments in the code.

Your HTML file should look something like:

<!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>Node-RED UI Builder</title>
    <meta name="description" content="Node-RED UI Builder - VueJS + bootstrap-vue version">

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

    <!-- See https://goo.gl/OOhYW5 -->
    <link rel="manifest" href="./manifest.json">
    <meta name="theme-color" content="#3f51b5">

    <!-- Used if adding to homescreen for Chrome on Android. Fallback for manifest.json -->
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="application-name" content="Node-RED UI Builder">

    <!-- Used if adding to homescreen for Safari on iOS -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="apple-mobile-web-app-title" content="Node-RED UI Builder">

    <!-- Homescreen icons for Apple mobile use if required
        <link rel="apple-touch-icon" href="./images/manifest/icon-48x48.png">
        <link rel="apple-touch-icon" sizes="72x72" href="./images/manifest/icon-72x72.png">
        <link rel="apple-touch-icon" sizes="96x96" href="./images/manifest/icon-96x96.png">
        <link rel="apple-touch-icon" sizes="144x144" href="./images/manifest/icon-144x144.png">
        <link rel="apple-touch-icon" sizes="192x192" href="./images/manifest/icon-192x192.png">
    -->

    <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 class="navbar"><span>Real-Time Chart with Plotly.js</span></div>
    <div class="wrapper">
    <div id="chart"></div>

<h2>Two Equal Columns</h2>

<div class="row">
  <div class="column" style="background-color:#aaa;">
    <div id="myProgress">
  <div class= "arr" id="myBar"></div>
  <div class="avv"> <h2 id="texte"></h2></div>

</div>
  </div>
  <div class="column" style="background-color:#bbb;">
  <img id="photo" src="http://localhost:1880/handgripSurTable.JPG">

  </div>
</div>

<br>
<button onclick="move()">Click Me</button> 

    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="../uibuilder/vendor/vue/dist/vue.js"></script> <!-- dev version with component compiler -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script>

    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>

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

   <!-- You are effectively replacing this...
    <script src="./index.js"></script> -->
<script>

function move() {
    var i = 0;
    var nbEtape = 21;
    var decompte1 = 3;
    var decompte2 = 8;
    var decompte3 = 13;
    var decompte4 = 18;
    var elem = document.getElementById("myBar");
    var photo = document.getElementById("photo");
    var texte = document.getElementById("texte");
    var width = 100;
    var message = [
    "Laisser l'appareil sur la table",
    "Prenez l'appareil dans la main droite et positionnez votre bras le long de votre corps comme sur la photo", 
    "Attention, préparez-vous à forcer", 
    "",
    "Forcez",
    "Reposez l'appareil sur la table, vous avez 30 secondes de récupération",
    "Reprenez l'appareil dans la main droite, et placez votre bras le long du corps comme précédemment (comme sur la photo)",
    "Attention, préparez-vous à forcez",
    "",
    "Forcez",
    "Reposez l'appareil sur la table, vous allez maintenant passer au test pour votre main gauche",
    "Prenez l'appareil dans la main gauche et positionnez votre bras le long de votre corps comme sur la photo",
     "Attention, préparez-vous à forcer", 
    "",
    "Forcez",
    "Reposez l'appareil sur la table, vous avez 30 secondes de récupération",
    "Reprenez l'appareil dans la main gauche, et placez votre bras le long du corps comme précédemment (comme sur la photo)",
    "Attention, préparez-vous à forcez",
    "",
    "Forcez",
    "Le test est terminé, vous pouvez reposer l'appareil sur la table et passez à l'atelier suivant"
    ];
    var image = [
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripMainDroite.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripMainGauche.JPG",
    "http://localhost:1880/handgripSurTable.JPG",
    ]
    var interval = [20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20];

    var messageMainDroite = "";
    var id = setInterval(frame, interval[i] );

      function frame() {
          width = width - 1;
          elem.style.width = width + "%";
          if (i == decompte1 || i == decompte2 || i == decompte3 || i == decompte4) {
            if (width > 66) {
            texte.textContent = '3';
            }
            if (width < 66 && width > 33) {
            texte.textContent = '2';
            }
            if (width < 33 && width > 0) {
            texte.textContent = '1';
            } 
          } else {
            texte.textContent = message[i];
            photo.setAttribute("src",image[i]);
          }

          if (width == 0 && i < nbEtape) {
            width = 100;
            i++;
            clearInterval(id);
            id = setInterval(frame, interval[i]);
          }
          if (i == nbEtape) {
            clearInterval(id);
          }
        }
    
    
      
   
  } // fin de move()

              function getData() {
                return Math.random();
            }  
            Plotly.plot('chart',[{
                y:[getData()],
                type:'line'
            }]);
            
            var cnt = 0;
            setInterval(function(){
                Plotly.extendTraces('chart',{ y:[[getData()]]}, [0]);
                cnt++;
                if(cnt > 500) {
                    Plotly.relayout('chart',{
                        xaxis: {
                            range: [cnt-500,cnt]
                        }
                    });
                }
            },15);
</script>

</body></html>

ok thank you for your help. Sorry for "stupid" question, I am trying to understand lot of things since I am quiet new in this area. So the "normal" javascript that I have used is usable inside the index.js of the uibuilder ?
I do not any about VueJS (I can try to learn how it works if needed...). I'd like to make my own custom webpage which interact with node-red since some node are really useful to get data from a load cell or even to write data inside a database...

No problem.

Yes, your JS code can be moved into index.js.

uibuilder is the right tool to use if you want a fully custom web interface with data exchange between Node-RED and your front-end browser code.

The following script links are absolutely required in your front-end code for uibuilder to do anything useful:

<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<script src="./uibuilderfe.min.js"></script>

The 1st gives you the dynamic data link between the server and the client using websockets. The 2nd is the uibuilder "magic" that ties everything together and makes it easier for you to deal with messages back and forth to node-red.

I encourage you to study the default template that comes with uibuilder and also look at the "Simple" uibuilder example that you can find on the node-red flows site as well as linked from the uibuilder WIKI.

The simple example in particular should help you understand how things slot together.

The WIKI also has some examples of using node-red, uibuilder and various charting libraries and that may help you as well. Just start with the simple example though so that you understand how the node-red server (back-end) and the front-end code (html/js in the browser) work and how they communicate over websockets . These basics will then really help you as you build your own custom pages because you will be more likely to remember where everything runs and how things communicate.

VueJS is simply a tool to let you build interactive and data-driven web pages (the example you are giving is a data-driven page). You could use one of many other frameworks such as the old classic of jQuery. However, using something like Vue or jQuery (or REACT or Angular, etc) lets you focus on your own logic.

If all you want to do is to send data to plotly, then you may not even need a framework so by all means try without.

To get uibuilder working with no other framework, you still need to load the two libraries I listed above. Then you need to start uibuilder and create a "listener" that listens for messages coming from Node-RED.

        uibuilder.start()

        uibuilder.onChange('msg', function(msg){
            console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)

            // do your own stuff here for when you get more data from Node-RED
        })

In your existing code, you are generating random data in the front-end and using setInterval to update the plot. You don't need to do that, update the plot from within the onChange function instead.

3 Likes

Ok great, thank you for all those information. I have now some work to do to assimilate all those concepts and make my own custom web page!
Thanks again !!

Thank you for all your help. Now I have my webpage with my data plot using plotly! Thank you!!

1 Like

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