Questions to 'charts' in UIBUILDER

My goal was to move from a NR -Desktop chart to an uibuilder chart.
Due to my limited experience in js an nearly no experience in html I was not aware that 'canvas' is one key for my solution.

So I asked AI for a proposal and got the main information. So I succeded, after a week of testing, investigating, frustrating, debugging, ...

You can see the result in the picture and the code for it below.

Bildschirmfoto vom 2024-06-08 16-32-04

[
    {
        "id": "6eb908405022da43",
        "type": "inject",
        "z": "c441b77c555275d8",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "2",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 250,
        "y": 380,
        "wires": [
            [
                "f66abe8c758b6825"
            ]
        ]
    },
    {
        "id": "f66abe8c758b6825",
        "type": "function",
        "z": "c441b77c555275d8",
        "name": "Update with random numbers",
        "func": "msg.payload = [Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 470,
        "y": 380,
        "wires": [
            [
                "49ffaa2ec54f8a2f"
            ]
        ]
    },
    {
        "id": "49ffaa2ec54f8a2f",
        "type": "uibuilder",
        "z": "c441b77c555275d8",
        "name": "",
        "topic": "",
        "url": "ui-test-9",
        "okToGo": true,
        "fwdInMessages": false,
        "allowScripts": false,
        "allowStyles": false,
        "copyIndex": true,
        "templateFolder": "blank",
        "extTemplate": "",
        "showfolder": false,
        "reload": false,
        "sourceFolder": "src",
        "deployedVersion": "6.8.2",
        "showMsgUib": false,
        "title": "",
        "descr": "",
        "x": 690,
        "y": 380,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "f3cd0e3ff17a3815",
        "type": "comment",
        "z": "c441b77c555275d8",
        "name": "index.html",
        "info": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>My Chart</title>\n\n    <!-- next line missing in AI -->\n    <script defer src=\"../uibuilder/uibuilder.iife.min.js\"></script>\n\n    <script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n    <link rel=\"stylesheet\" href=\"index.css\">\n    <script src=\"index.js\" ></script>\n</head>\n\n<body>\n    <h1>Chart Example</h1>\n    <canvas id=\"myChart\" width=\"400\" height=\"200\"></canvas>\n    <!-- 'defer' missing in next line from AI -->\n    <script defer src=\"index.js\"></script>\n</body>\n\n</html>",
        "x": 560,
        "y": 280,
        "wires": []
    },
    {
        "id": "0e67de5647fea05c",
        "type": "comment",
        "z": "c441b77c555275d8",
        "name": "index.js",
        "info": "document.addEventListener('DOMContentLoaded', (event) => {\n    // Data for the chart\n    const data = {\n        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n        datasets: [{\n            label: 'My First dataset',\n            backgroundColor: 'rgba(255, 99, 132, 0.2)',\n            borderColor: 'rgba(255, 99, 132, 1)',\n            borderWidth: 1,\n            data: [65, 59, 80, 81, 56, 55, 40],\n        }]\n    };\n\n    // Configuration for the chart\n    const config = {\n        type: 'bar',\n        data: data,\n        options: {\n            scales: {\n                y: {\n                    beginAtZero: true\n                }\n            }\n        }\n    };\n\n    // Render the chart\n    myChart = new Chart(\n        document.getElementById('myChart'),\n        config\n    );\n});\n\n// Listen for messages from Node-RED\nuibuilder.onChange('msg', function (msg) {\n    // Update chart data on receiving new messages\n    myChart.data.datasets[0].data = msg.payload;\n    myChart.update();\n});",
        "x": 670,
        "y": 300,
        "wires": []
    },
    {
        "id": "a1bce227565e2317",
        "type": "comment",
        "z": "c441b77c555275d8",
        "name": "index.css",
        "info": "body {\n    font-family: Arial, sans-serif;\n    margin: 20px;\n}\n\nh1 {\n    text-align: center;\n}\n\ncanvas {\n    display: block;\n    margin: 0 auto;\n}",
        "x": 780,
        "y": 320,
        "wires": []
    }
]

Now I want gain a better understanding. During the investigation I got some question marks in my mind, perhaps you can help me:

Question 1:
If I edit 'index.js' I see that several names are 'undefined'. Coming from Pascal and C I would say they are not 'declared'. At least they are not declared in 'index.js'. (see picture)

Bildschirmfoto vom 2024-06-08 11-23-53

But it seems to be some kind of 'zombies' :wink:, because the code is running.

Can anybody tell me how this works?

Question 2:
In AI's proposal 'defer' is missing in '<script defer src="index.js"></script>'.
My understanding is, that 'defer' affects the first time execution of scripts, if it is executed during parsing, or not.
My observation in the debugger is, that 'uibuilder.onChange' is not available if 'defer' is not set. 'Not available' means: I cannot set a break point here. And as a matter of course this part is not executed if the 'injcect' fires.
IF 'defer' is set, everything works fine.

Can anybody tell me?

Question 3:
When inspecting 'myChart' there is no 'data' field in it. My data are in 'myChart.config._config.data.datasets[0].data'.
But the update method 'uibuilder.onChange(...)' has 'myChart.data.datasets[0].data = msg.payload;'.


(the screen shot shows the important part of the object 'myChart'.)

Can anybody tell me the mechanism?

Sorry. I know. It's a lot...

I had a great experience by using Google Charts with uibuilder...

Congratulations for making the smart move. :grin:

Canvas is one method for adding graphics to a web page, svg is the other main way (other than images).

Either approach is applicable for showing charts on a page and different charting libraries will use different approaches.

I will certainly try.

Yes, certainly. It is "only" the editor that is telling you that it doesn't know about them, this is a false positive. It happens simply because the Monaco editor, in this case, has no way of knowing that those global variables are defined elsewhere. There are ways that you could try to let the editor know that they exist though in practice, this can take some effort and, in this case anyway, is probably not worth it.

Do remember here that the code you are editing is on the server whereas it RUNs in the browser. So it isn't that surprising that there is an "understanding" gap between the editor and the code.

The links to the JavaScript libraries you have in your HTML not only load the library code into browser memory but those libraries then normally self-execute to make their use easy. In doing so, they create the global variables you are seeing. This "magic" is far from obvious at first sight and is rarely explicitly documented.

If you were to examine much of the example browser javascript code on the Internet, you will find many people recommending that you put the javascript at the end of the <body> section of you html. In days past, this was a lot more efficient because <link>d javascript in the head section delayed the loading and rendering of the visible HTML, it wasn't very perfomant at all.

Which is why JavaScript was updated to allow the defer keyword which does exactly what it says. It makes it very similar to putting the links at the end of the body. However, it has the added benefit of keeping the javascript links separate from the visible HTML layout. I find this approach much better and cleaner and so in a previous update to UIBUILDER, I changed the default templates to use this newer style.

Unfortunately, the LLM's that ChatGPT et-al use are not actually "intelligent" in any creative sense. They rely on existing writing and code done by people in the past. So they tend to not use the defer keyword. This and existing examples perpetuate a now myth about the best way to load JavaScript.

If you don't want to usedefer, you should move all of the javascript links down to the end of your HTML.

I'm not quite sure why that is actually, everything should still work without the defer keyword - just not efficienly.

Hard to do without knowing which charting library you are using.

In general, it is worth knowing that JavaScript is a bit of a strange language at times. The myChart object might possibly have things in it that are not visible to the console.log debug output that I assume you've used.

Perhaps try console.log(myChart.data)? It does strike me that myChart.data might be some kind of defined shortcut to make it easier to use.

Let me know what chart library you are using though if you want me to check it out.

Not at all, JavaScript is not the easiest language to get your head around (past the basics anyway) - especially if you have come from more formal languages.

PS: You could always ask your favourite LLM tool to describe the differences between links with and without the defer attribute. It would, in fact, be interesting to hear what they say.

@TotallyInformation Thank's a lot for spending your time to read my post and answering my questions. It's really helpful!

That's really a good point. I totally missed it. Mostly I work completely local or edit local and debug on a remote system.
I checked this in the browsers debugger (firefox: F12) and here is everything fine.

I invested some more time to understand the data issue and found, that both works:
myChart.data.datasets[0].data = msg.payload;
as well as
myChart.config._config.data.datasets[0].data= msg.payload;.
So it is as it is and if I follow the doc of canvas in the future, I'm hopefully on the save side.

The 'defer' issue is still not fully understood, here I have to spend some more time. But the important thing is that it works, if it is set........

I do not use any special library. I even just did quick check and installed a new nodered instance (proxmox/lxc) and installed in nr nothing but 'node-red-contrib-uibuilder' and my code from above. 'defer' at any position (head/top, head/bottom, body): if present the 'uibuilder'- block is present, if not present, the block is not.

Btw: Now I know that this 'defer' issue is the main reason for my confusion. I was forced to debug problems started :grinning: But I learned a lot.

A couple of links to help:

Perhaps the following code makes my 'question mark' it a little visible to you. It is the above example. I just added a button in the html and the related function in js.

[
    {
        "id": "6eb908405022da43",
        "type": "inject",
        "z": "c441b77c555275d8",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 240,
        "y": 380,
        "wires": [
            [
                "f66abe8c758b6825"
            ]
        ]
    },
    {
        "id": "f66abe8c758b6825",
        "type": "function",
        "z": "c441b77c555275d8",
        "name": "Update with random numbers",
        "func": "msg.payload = [Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 470,
        "y": 380,
        "wires": [
            [
                "49ffaa2ec54f8a2f"
            ]
        ]
    },
    {
        "id": "49ffaa2ec54f8a2f",
        "type": "uibuilder",
        "z": "c441b77c555275d8",
        "name": "",
        "topic": "",
        "url": "ui-test-9",
        "okToGo": true,
        "fwdInMessages": false,
        "allowScripts": false,
        "allowStyles": false,
        "copyIndex": true,
        "templateFolder": "blank",
        "extTemplate": "",
        "showfolder": false,
        "reload": false,
        "sourceFolder": "src",
        "deployedVersion": "6.8.2",
        "showMsgUib": false,
        "title": "",
        "descr": "",
        "x": 690,
        "y": 380,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "f3cd0e3ff17a3815",
        "type": "comment",
        "z": "c441b77c555275d8",
        "name": "index.html",
        "info": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <title>GCh - Chart</title>\n    <meta charset=\"UTF-8\">\n\n    <script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n    <script defer src=\"../uibuilder/uibuilder.iife.min.js\"></script>    \n    \n    <link rel=\"stylesheet\" href=\"index.css\">\n    \n</head>\n\n<body>\n    <h1>Chart Example</h1>\n    <canvas id=\"myChart\" width=\"400\" height=\"200\"></canvas>\n\n    <script defer src=\"index.js\"></script>\n\n    <button type=\"button\" onclick=\"newValues()\"> Werte ändern  </button>\n\n</body>\n\n</html>",
        "x": 560,
        "y": 280,
        "wires": []
    },
    {
        "id": "0e67de5647fea05c",
        "type": "comment",
        "z": "c441b77c555275d8",
        "name": "index.js",
        "info": "document.addEventListener('DOMContentLoaded', (event) => {\n    // Data for the chart\n    const data = {\n        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n        datasets: [{\n            label: 'My First dataset',\n            backgroundColor: 'rgba(255, 99, 132, 0.2)',\n            borderColor: 'rgba(255, 99, 132, 1)',\n            borderWidth: 1,\n            data: [65, 59, 80, 81, 56, 55, 40],\n        }]\n    };\n\n    // Configuration for the chart\n    const config = {\n        type: 'bar',\n        data: data,\n        options: {\n            scales: {\n                y: {\n                    beginAtZero: true\n                }\n            }\n        }\n    };\n\n    // Render the chart\n    myChart = new Chart(\n        document.getElementById('myChart'),\n        config\n    );\n});\n\n// Listen for messages from Node-RED\nuibuilder.onChange('msg', function (msg) {\n    myChart.data.datasets[0].data = msg.payload;\n    myChart.update();\n\n// dummy code just to give a target for a breakpoint\n    var t = 10;\n    if (t==10)\n      t = 0;\n});\n\nfunction newValues() {\n    myChart.data.datasets[0].data = [50, 60, 70, 80, 90, 100, 110];\n    myChart.update();\n}\n",
        "x": 670,
        "y": 300,
        "wires": []
    },
    {
        "id": "a1bce227565e2317",
        "type": "comment",
        "z": "c441b77c555275d8",
        "name": "index.css",
        "info": "body {\n    font-family: Arial, sans-serif;\n    margin: 20px;\n}\n\nh1 {\n    text-align: center;\n}\n\ncanvas {\n    display: block;\n    margin: 0 auto;\n}",
        "x": 780,
        "y": 320,
        "wires": []
    }
]

This function obviously has no access to NR. I understand that this is the main feature of UIBUILDER.
But the function is available if 'defer' is set or not, whereas it is not the case for 'uibuilder.onChange(...)'

At the end it is not important, I just use 'defer'. But perhaps it's an issue in 'uibuilder'. (?)

OK, lets start by tidying up your HTML:

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

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="../uibuilder/images/node-blue.ico">

    <title>GCh - Chart - Node-RED uibuilder</title>
    <meta name="description" content="Node-RED uibuilder - GCh - Chart">

    <!-- Your own CSS (defaults to loading uibuilders css)-->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->
    <script defer src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script defer src="../uibuilder/uibuilder.iife.min.js"></script>
    <script defer src="./index.js"></script>
    <!-- #endregion -->

</head><body class="uib">
    
    <h1 class="with-subtitle">GCh - Chart</h1>
    <div role="doc-subtitle">Using the UIBUILDER IIFE library.</div>

    <div id="more"></div>

    <canvas id="myChart" width="400" height="200"></canvas>

    <button type="button" onclick="newValues()"> Werte ändern  </button>

</body></html>

You either use defer in the head OR you put the js load (without defer) at the end of the body. The defer keyword effectively does the same thing as putting the link at the end of the body, it is just neater and more in line with modern standards.

If you put the link in the head without defer but still leave defer on the load of the uibuilder library, things will happen out of order - your index.js would load before the uibuilder library and so you could not use the uibuilder global at all.

Similarly, leaving the uibuilder library in the head with the defer on it and loading the others in the body will result in the uibuilder library loading last and therefore out of order.

Libraries, as it states in the comments for the default template, MUST be loaded in the right order or things won't work. The defer keyword changes the order when used in the <head> HTML section and does nothing in the <body>.

Nope :grinning:

It is simply the way that the browser loads things.

By the way:

Yes, you do, you are using the ChartJS library.