UI Chart Node - Data Retrieval

Hi All,

I am hoping there is a method to retrieve data from the chart node....

When the cursor hovers over a valid data point, a "pop-up" exposes the information relevant to that point of the chart, ie:

Screenshot_20211031_130150

This would be particularly helpful for me to process externally at the click of a mouse....

Many thanks
Ed

I think the only way you can do that is to use the ui_template node and create the Chartjs yourself and then you can define an onClick event in the charts options in order to get the values you need.

Example Flow :

[{"id":"d58b635f1bfdaa7f","type":"ui_template","z":"5847b7aa62131d37","group":"6efcc19883dcdf68","name":"","order":0,"width":"24","height":"20","format":"<div class=\"chart-container\" style=\"position: relative; height:40vh; width:50vw\">\n    <canvas id=\"myChart\"></canvas>\n</div>\n\n<script>\n    (function(scope) {\n\nvar ctx = document.getElementById('myChart').getContext('2d');\nvar myChart = new Chart(ctx, {\n    type: 'bar',\n    data: {\n        labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n        datasets: []\n    },\n    options: {\n        scales: {\n            y: {\n                beginAtZero: true\n            }\n        },\n        responsive: true,\n\n        onClick(e) {\n            const activePoints = this.getElementAtEvent(e)[0];\n            var index = activePoints._index;\n            var datasetIndex = activePoints._datasetIndex;\n            const value = this.data.datasets[datasetIndex].data[index]\n\n            scope.send({ payload:  value })\n        }\n    },\n    \n  \n});\n\n\n// addData \n  function addData(chart, data) {\n   // chart.data.labels.push(label);\n   if(chart.data.datasets.length > 10) {\n       chart.data.datasets.shift()\n   }\n    chart.data.datasets.push(data);\n    chart.update();\n}\n  \n// watch for msg\nscope.$watch('msg', (msg) => {\n   if (msg) {\n      // Do something when msg arrives\n     addData(myChart, msg.payload)\n    }\n})\n\n\n})(scope)\n\n</script>","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","className":"","x":740,"y":480,"wires":[["d1850c4a1e48066e"]]},{"id":"53e059241791ed04","type":"inject","z":"5847b7aa62131d37","name":"trigger","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":340,"y":480,"wires":[["17c879d6076d6411"]]},{"id":"17c879d6076d6411","type":"function","z":"5847b7aa62131d37","name":"Chart Data","func":"msg.payload = {\n    label: '# of Votes',\n    data: [\n        12 + Math.random() * 2,\n        19 + Math.random() * 2,\n        3 + Math.random() * 2,\n        5 + Math.random() * 2,\n        2 + Math.random() * 2,\n        3 + Math.random() * 2],\n    backgroundColor: [\n        'rgba(255, 99, 132, 0.2)',\n        'rgba(54, 162, 235, 0.2)',\n        'rgba(255, 206, 86, 0.2)',\n        'rgba(75, 192, 192, 0.2)',\n        'rgba(153, 102, 255, 0.2)',\n        'rgba(255, 159, 64, 0.2)'\n    ],\n    borderColor: [\n        'rgba(255, 99, 132, 1)',\n        'rgba(54, 162, 235, 1)',\n        'rgba(255, 206, 86, 1)',\n        'rgba(75, 192, 192, 1)',\n        'rgba(153, 102, 255, 1)',\n        'rgba(255, 159, 64, 1)'\n    ],\n    borderWidth: 1\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":480,"wires":[["d58b635f1bfdaa7f","0869cd746de10613"]]},{"id":"0869cd746de10613","type":"debug","z":"5847b7aa62131d37","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":620,"y":380,"wires":[]},{"id":"d1850c4a1e48066e","type":"debug","z":"5847b7aa62131d37","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":480,"wires":[]},{"id":"6efcc19883dcdf68","type":"ui_group","name":"Chart JS","tab":"39a6d442788cfb84","order":1,"disp":true,"width":"24","collapse":false,"className":""},{"id":"39a6d442788cfb84","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]
1 Like

Thanks Andy,

Will give it a go after putting my pc back together after a "forced reload" thanks to an abortive (where did the internet disappear to) online update of the O/S.... mutter mutter...

Ed

Ok... Wow! Again Wow!!

Your example puts out the msg.payload with the data at cursor, exactly what I was hoping is possible...

Do you think it is possible to apply a ?css template? to the existing chart node to extract similar data?

I can see that this portion of your code pertains to retrieval of the data:

 onClick(e) {
            const activePoints = this.getElementAtEvent(e)[0];
            var index = activePoints._index;
            var datasetIndex = activePoints._datasetIndex;
            const value = this.data.datasets[datasetIndex].data[index]

            scope.send({ payload:  value })
        }

I follow the general areas (in a very basic way) of the flow of the code under the script section where the chart is created, where data is added, where the template waits for an incoming message to add to the data.... On the chart node that I have used in my flow, all of this is of similar ilk from what I can surmise, looking at what I have to feed the ui-chart node... but I am waaaaay out of the comfort zone here!!

(I haven't even managed to mentally grasp the <div class> nudge you gave me previously :cry:)

Regds
Ed

to the existing chart that is using the Dashboard ui_chart ? .. i dont think it can support these click events as it is. Thats why i used in my example a ui_template and created the chartjs from scratch in order to define those additional properties.

But you studied the code and saw where we $watch for incoming msgs and update the chart.
so we have to see what msgs you were sending to the ui_chart and try to replicate that for the ui_template example.

Thanks for coming back to me!

I am sending an array, please forgive my coding, here is the node preceding the chart:

[
    {
        "id": "6e2fc0aad76d2ddb",
        "type": "function",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "Append Data",
        "func": "freqsigctl = flow.get('freqsigctl')\nsweepenabled = flow.get('sweepenabled')\nsweepcount = flow.get('sweepcount');// Set of scan data as passed forward, max 5\n//sweepcount = 1;//Currently only single sweep enabled\nif (freqsigctl == \"auto\"){context.set('auto',true)}else{context.set('auto',false)}\n\nif (msg.topic == \"clear\"){\n    context.set('m',undefined)\n    m={};\n    m.labels= []\n    m.series = [];\n    m.data = [];\n    m.data[0] = [];//Freq\n    m.data[1] = [];//S1\n    m.data[2] = [];//S2\n    m.data[3] = [];//S3\n    m.data[4] = [];//S4\n    m.data[5] = [];//S5\n    m.data[6] = [];//S6\n    m.data[7] = [];//S7\n    \n    m.labels.push(0)//frequency Zero'd to start\n    m.series.push('GHz','S');//S Meter Label\n    m.data[0].push(0);//Frequency ghz\n    m.data[1].push(0);//S meter Reading 1 Zero'd to start\n    m.data[2].push(0);//S meter Reading 2 Zero'd to start\n    m.data[2].push(0);//S meter Reading 3 Zero'd to start\n    m.data[3].push(0);//S meter Reading 4 Zero'd to start\n    m.data[5].push(0);//S meter Reading 5 Zero'd to start\n    \n    context.set('m',m);\n    node.send(msg)\n    return msg\n}\nif (freqsigctl == \"pause\"){\n    pause = 1\n    context.set(\"pause\",pause)\n}\nif (freqsigctl == \"run\"){\n    pause = 0\n    context.set(\"pause\",0)\n}\n\n\nif(context.get('auto')){\n    if(sweepenabled == 0){\n        return\n    }\n    if(flow.get(\"squelched\") == false && flow.get(\"sweepsqlhalt\") == true){\n        return    \n    }\n    \n    pause=0\n}\n\nif(pause == 1){\n    return\n}\n\nsigstrength = msg.payload.sigstrength\nfrequency = msg.payload.frequency\nm=context.get(\"m\")\nif(sweepcount == 1){\n    m.labels.push(frequency)//frequency\n    m.series.push('GHz','S1','S2','S3','S4','S5','S6','S7');//S Meter Label\n    m.data[0].push(frequency/1000);//Frequency ghz\n}\nm.data[sweepcount].push(sigstrength);//S meter Reading 1/2/3/4/5\n/*\n*/\n    \ncontext.set(\"m\",m)\nreturn {payload:[m]};",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 790,
        "y": 2650,
        "wires": [
            [
                "f4ef149d3570d66b"
            ]
        ]
    }
]

Effectively it relies on a few flow variables to determine whether it should be in pause, run, clear or auto mode...

It is constantly getting a frequency and strength (S) pair of data values at a reasonably regular interval and determines whether the chart should be actively updated by them by looking at flow variables...

Of note, the chart node "is told" that there is a second scan in progress by the "sweepcount" variable which it then uses to advance to the next ?dimension/range? in the array, going from m.data[1] to m.data[2] and so on... I only cater for up to 7 consecutive scans... (m.data[0] contains the list of frequencies scanned on the first pass, paired with the signal strengths in m.data[1], to which the subsequent scan signal strengths are added as m.data[2] /3/4 etc... I do not try and match the current frequency with the numbers in m.data[0], instead relying on the fact that the frequency steps for each run are duplicated so the strength reading "pairs up" with the scanned frequencies of the previous runs... the frequency range and frequency step size of each and subsequent run is locked whilst the set of scans is in progress...)

A bit vague, sorry! Hope you can grasp my gibberish!!

Regds
Ed

Your function depends on many things that we cannot replicate.
Everything can be clearer if you can copy/paste a msg from a Debug node after the "Append Data" node.
Use the icon image

Hope I have this right!!

{"payload":[{"labels":[null,9.405,9.41,9.415,9.42,9.424999,9.43,9.435,9.44,9.445,9.45],"series":["GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7","GHz","S1","S2","S3","S4","S5","S6","S7"],"data":[[0,null,0.009405,0.00941,0.009415,0.00942,0.009424999,0.00943,0.009435,0.009439999999999999,0.009445,0.00945],[0,null,8.8,8.1,8.19,8.1,7,6.2,5.8,6.3,6.6,7],[0,7.8,7.9,8.1,7.6,6.8,5.9,5.4,7.2,6.8,6.8],[0,7.1],[0],[0],[0],[0]]}],"_msgid":"aba71d9507b86ccc"}

hi .. the message is posted fine but the data seem to be inconsistent
there are some data arrays that dont have the same length as the x-axis labels and repeated values in series. I dont know how to help with that but try to modify your "Append Data" function in order to structure your chart data as documented here
If you dont have values for some y-axis points, fill them with null (the chart should skip those points as in my example)

Example:

[{"id":"d58b635f1bfdaa7f","type":"ui_template","z":"5847b7aa62131d37","group":"6efcc19883dcdf68","name":"","order":0,"width":"24","height":"20","format":"<div class=\"chart-container\" style=\"position: relative; height:40vh; width:50vw\">\n    <canvas id=\"myChart\"></canvas>\n</div>\n\n<script>\n    (function(scope) {\n\nvar ctx = document.getElementById('myChart').getContext('2d');\nvar myChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        labels: [],\n        datasets: []\n    },\n    options: {\n    //    scales: {\n    //   x: {display: true,},\n    //   y: {display: true,type: 'logarithmic' }\n    // },\n        responsive: true,\n\n        onClick(e) {\n            const activePoints = this.getElementAtEvent(e)[0];\n            var index = activePoints._index;\n            var datasetIndex = activePoints._datasetIndex;\n            const value = this.data.datasets[datasetIndex].data[index]\n\n            scope.send({ payload:  value })\n        }\n    },\n\n});\n\n\n// watch for msg\nscope.$watch('msg', (msg) => {\n   if (msg) {\n      // Do something when msg arrives\n    myChart.clear();\n    // msg.payload.labels.forEach( el => myChart.data.labels.push(el))\n    // msg.payload.datasets.forEach( el => myChart.data.datasets.push(el))\n    myChart.data.labels = [...msg.payload.labels]\n    myChart.data.datasets = [...msg.payload.datasets]\n    console.log(myChart.data)\n    myChart.update();\n   \n\n    }\n})\n\n\n})(scope)\n\n</script>","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","className":"","x":740,"y":520,"wires":[["d1850c4a1e48066e"]]},{"id":"0869cd746de10613","type":"debug","z":"5847b7aa62131d37","name":"1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":470,"y":440,"wires":[]},{"id":"d1850c4a1e48066e","type":"debug","z":"5847b7aa62131d37","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":930,"y":520,"wires":[]},{"id":"8ef09fcd8ba00562","type":"inject","z":"5847b7aa62131d37","name":"chart data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"labels\":[9,9.405,9.41,9.415,9.42,9.424999,9.43,9.435,9.44,9.445,9.45],\"series\":[\"GHz\",\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\",\"S7\"],\"data\":[[null,0.009405,0.00941,0.009415,0.00942,0.009424999,0.00943,0.009435,0.009439999,0.009445,0.00945],[0,0,8.8,8.1,8.19,8.1,7,6.2,5.8,6.3,6.6],[0,7.8,7.9,8.1,7.6,6.8,5.9,5.4,7.2,6.8,6.8],[0,0.009405,0.00941,0.009415,null,null,0.00943,3.009435,0.009439999,0.009445,1.00945],[0,0.009405,0.00941,7.009415,0.00942,null,5.00943,0.009435,9.009439999,0.009445,0.00945],[1,0.009405,0.00941,5.009415,0.00942,0.009424999,4.00943,0.009435,null,0.009445,0.00945],[0,6.009405,0.00941,0.009415,5.00942,0.009424999,0.00943,0.009435,1.009439999,0.009445,0.00945],[0,0.009405,1.00941,3.009415,0.00942,0.009424999,8.00943,0.009435,0.009439999,0.009445,1.00945],[0,4.009405,0.00941,0.009415,5.00942,0.009424999,0.00943,0.009435,5.009439999,0.009445,0.00945],[0,0.009405,0.00941,0.009415,5.00942,0.009424999,0.00943,6.009435,0.009439999,0.009445,0.00945],[0,1.009405,0.00941,0.009415,0.00942,3.009424999,0.00943,4.009435,0.009439999,0.009445,6.00945]]}","payloadType":"json","x":370,"y":520,"wires":[["0869cd746de10613","44bc844c593296ed"]]},{"id":"44bc844c593296ed","type":"function","z":"5847b7aa62131d37","name":"","func":"const CHART_COLORS = [\n    'rgb(255, 99, 132)',\n    'rgb(255, 159, 64)',\n    'rgb(255, 205, 86)',\n    'rgb(75, 192, 192)',\n    'rgb(54, 162, 235)',\n    'rgb(153, 102, 255)',\n    'rgb(201, 203, 207)',\n    'rgb(255, 99, 132)',\n    'rgb(255, 159, 64)',\n    'rgb(255, 205, 86)',\n    'rgb(75, 192, 192)',\n    'rgb(54, 162, 235)',\n    'rgb(153, 102, 255)',\n    'rgb(201, 203, 207)'\n];\n\n\nmsg.payload.datasets = []\nmsg.payload.labels.forEach((el, index) => {\n    msg.payload.datasets.push({\n        label: msg.payload.series[index],\n        data: msg.payload.data[index],\n        fill: false,\n        borderColor: CHART_COLORS[index],\n        tension: 0.1\n    })\n})\n\ndelete msg.payload.series\ndelete msg.payload.data\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":520,"wires":[["d58b635f1bfdaa7f","1864055ce6e5ea3b"]]},{"id":"1864055ce6e5ea3b","type":"debug","z":"5847b7aa62131d37","name":"2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":710,"y":440,"wires":[]},{"id":"6efcc19883dcdf68","type":"ui_group","name":"Chart JS","tab":"39a6d442788cfb84","order":1,"disp":true,"width":"24","collapse":false,"className":""},{"id":"39a6d442788cfb84","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

Discovered that late last night, after I posted it.... I messed something up...

Let me see if I can sort the source data collection out first... It might take a while.... Under normal conditions, the x axis length is the same, but during the scan, as the data builds m.data[0]/[1] build up simultaneously, then on the next scan cycle, [2] starts getting populated and so on... if the scan is stopped, it will be a case that [0]/[1] will be the same and then if cycle 2/3/4/5/ etc are completed, the number of points will match... leaving the last one, ie [6] only partly populated....

Let me go dig and see where I have messed up...

Ok... Rewritten the whole "append routine" thingy... got it just peachy now...

Here are data samples of what I get out of it:

  1. Before Scan starts (Just been cleared)
{"labels":[null],"series":["GHz","S1","S2","S3","S4","S5","S6","S7"],"data":[[null],[null],[null],[null],[null],[null],[null],[null]]}

2)During the scan, before the first pass is complete

{"labels":[null,102.51,102.52,102.53,102.54,102.55,102.56,102.57,102.58,102.59,102.6,102.61,102.62,102.63,102.64,102.65,102.66,102.67,102.68],"series":["GHz","S1","S2","S3","S4","S5","S6","S7"],"data":[[null,0.10251,0.10252,0.10253,0.10254,0.10255,0.10256,0.10257,0.10258,0.10259,0.1026,0.10260999999999999,0.10262,0.10263,0.10264,0.10265,0.10266,0.10267,0.10268000000000001],[null,1.3,0.8,1.3,1.3,1.8,3.1,3.1,2.7,3.3,3.4,3.4,3.8,3.8,3.8,3.6,3.6,3.8,4.2],[null],[null],[null],[null],[null],[null]]}

3)First scan complete, starting the second:

{"labels":[null,102.51,102.52,102.53,102.54,102.55,102.56,102.57,102.58,102.59,102.6,102.61,102.62,102.63,102.64,102.65,102.66,102.67,102.68,102.69,102.7,102.71,102.72,102.73,102.74,102.75,102.76,102.77,102.78,102.79,102.8,102.81,102.82,102.83,102.84,102.85,102.86,102.87,102.88,102.89,102.9,102.91,102.92,102.93,102.94,102.95,102.96,102.97,102.98,102.99,103,103.01,103.02,103.03,103.04,103.05,103.06,103.07,103.08,103.09,103.1,103.11,103.12,103.13,103.14,103.15,103.16,103.17,103.18,103.19,103.2,103.21,103.22,103.23,103.24,103.25,103.26,103.27,103.28,103.29,103.3,103.31,103.32,103.33,103.34,103.35,103.36,103.37,103.38,103.39,103.4,103.41,103.42,103.43,103.44,103.45,103.46,103.47,103.48,103.49,103.5],"series":["GHz","S1","S2","S3","S4","S5","S6","S7"],"data":[[null,0.10251,0.10252,0.10253,0.10254,0.10255,0.10256,0.10257,0.10258,0.10259,0.1026,0.10260999999999999,0.10262,0.10263,0.10264,0.10265,0.10266,0.10267,0.10268000000000001,0.10269,0.1027,0.10271,0.10272,0.10273,0.10274,0.10275,0.10276,0.10277,0.10278,0.10279,0.1028,0.10281,0.10282,0.10283,0.10284,0.10285,0.10286,0.10287,0.10288,0.10289,0.1029,0.10291,0.10292,0.10293000000000001,0.10294,0.10295,0.10296,0.10296999999999999,0.10298,0.10299,0.103,0.10301,0.10302,0.10303,0.10304,0.10305,0.10306,0.10307,0.10308,0.10309,0.1031,0.10311,0.10312,0.10313,0.10314,0.10315,0.10316,0.10317,0.10318000000000001,0.10319,0.1032,0.10321,0.10321999999999999,0.10323,0.10324,0.10325,0.10326,0.10327,0.10328,0.10329,0.1033,0.10331,0.10332,0.10333,0.10334,0.10335,0.10336,0.10337,0.10338,0.10339,0.1034,0.10341,0.10342,0.10343000000000001,0.10344,0.10345,0.10346,0.10346999999999999,0.10348,0.10349,0.1035],[null,1.3,0.8,1.3,1.3,1.8,3.1,3.1,2.7,3.3,3.4,3.4,3.8,3.8,3.8,3.6,3.6,3.8,4.2,4.2,3.6,3.4,4,4.2,3.4,3.4,3.6,3.8,4,4.2,5.4,4.7,4.8,6.2,6.5,6.3,6.6,6.8,7.2,7.4,7.6,7.6,7.8,7.9,7.8,7.9,7.8,7.6,7.6,7.8,7.8,7.8,7.9,7.9,7.8,7.8,7.8,7.6,7.6,7.6,7.4,7.4,7.4,7.3,6.7,6.6,6.4,5.9,6,5.4,5.3,3.6,2.7,4,2.7,0,0,0,0,0,0,0,0,0,0,0,1.8,1.8,2.2,3.1,3.3,3.3,3.3,3.4,3.4,3.3,3.6,3.6,3.6,3.4,3.4],[null,3.4,0.8,1.3,1.8,1.8,2.7,2.2,2.7,3.1,3.1,3.4,3.6,4,3.8,3.6,3.8,3.8,3.8,4,3.8,4,3.8,3.6],[null],[null],[null],[null],[null]]}

4)Third and final scan done, this could be up to seven scans though:

{"labels":[null,102.51,102.52,102.53,102.54,102.55,102.56,102.57,102.58,102.59,102.6,102.61,102.62,102.63,102.64,102.65,102.66,102.67,102.68,102.69,102.7,102.71,102.72,102.73,102.74,102.75,102.76,102.77,102.78,102.79,102.8,102.81,102.82,102.83,102.84,102.85,102.86,102.87,102.88,102.89,102.9,102.91,102.92,102.93,102.94,102.95,102.96,102.97,102.98,102.99,103,103.01,103.02,103.03,103.04,103.05,103.06,103.07,103.08,103.09,103.1,103.11,103.12,103.13,103.14,103.15,103.16,103.17,103.18,103.19,103.2,103.21,103.22,103.23,103.24,103.25,103.26,103.27,103.28,103.29,103.3,103.31,103.32,103.33,103.34,103.35,103.36,103.37,103.38,103.39,103.4,103.41,103.42,103.43,103.44,103.45,103.46,103.47,103.48,103.49,103.5],"series":["GHz","S1","S2","S3","S4","S5","S6","S7"],"data":[[null,0.10251,0.10252,0.10253,0.10254,0.10255,0.10256,0.10257,0.10258,0.10259,0.1026,0.10260999999999999,0.10262,0.10263,0.10264,0.10265,0.10266,0.10267,0.10268000000000001,0.10269,0.1027,0.10271,0.10272,0.10273,0.10274,0.10275,0.10276,0.10277,0.10278,0.10279,0.1028,0.10281,0.10282,0.10283,0.10284,0.10285,0.10286,0.10287,0.10288,0.10289,0.1029,0.10291,0.10292,0.10293000000000001,0.10294,0.10295,0.10296,0.10296999999999999,0.10298,0.10299,0.103,0.10301,0.10302,0.10303,0.10304,0.10305,0.10306,0.10307,0.10308,0.10309,0.1031,0.10311,0.10312,0.10313,0.10314,0.10315,0.10316,0.10317,0.10318000000000001,0.10319,0.1032,0.10321,0.10321999999999999,0.10323,0.10324,0.10325,0.10326,0.10327,0.10328,0.10329,0.1033,0.10331,0.10332,0.10333,0.10334,0.10335,0.10336,0.10337,0.10338,0.10339,0.1034,0.10341,0.10342,0.10343000000000001,0.10344,0.10345,0.10346,0.10346999999999999,0.10348,0.10349,0.1035],[null,1.3,0.8,1.3,1.3,1.8,3.1,3.1,2.7,3.3,3.4,3.4,3.8,3.8,3.8,3.6,3.6,3.8,4.2,4.2,3.6,3.4,4,4.2,3.4,3.4,3.6,3.8,4,4.2,5.4,4.7,4.8,6.2,6.5,6.3,6.6,6.8,7.2,7.4,7.6,7.6,7.8,7.9,7.8,7.9,7.8,7.6,7.6,7.8,7.8,7.8,7.9,7.9,7.8,7.8,7.8,7.6,7.6,7.6,7.4,7.4,7.4,7.3,6.7,6.6,6.4,5.9,6,5.4,5.3,3.6,2.7,4,2.7,0,0,0,0,0,0,0,0,0,0,0,1.8,1.8,2.2,3.1,3.3,3.3,3.3,3.4,3.4,3.3,3.6,3.6,3.6,3.4,3.4],[null,3.4,0.8,1.3,1.8,1.8,2.7,2.2,2.7,3.1,3.1,3.4,3.6,4,3.8,3.6,3.8,3.8,3.8,4,3.8,4,3.8,3.6,3.8,3.8,3.4,3.6,3.8,4,3.8,4.5,5.3,5.4,5.9,6.4,6.9,7.2,7.4,7.4,7.6,7.4,7.6,7.8,7.8,7.8,7.8,7.6,7.6,7.6,7.4,7.4,7.6,7.6,7.8,7.6,7.4,7.4,7.4,7.4,7.3,7.2,7.1,6.9,6.8,6.9,6.5,6,5.4,5.3,4.5,3.6,3.6,3.4,0.4,0,0,0,0,0,0,0,0,0,0,0.4,0.8,1.8,3.1,3.3,3.4,3.4,3.4,3.4,3.4,3.6,3.8,3.8,3.8,3.3,3.4],[null,3.3,1.3,2.2,2.7,3.1,3.3,3.6,3.3,3.3,3.4,3.4,3.4,3.6,3.8,4,3.4,3.3,3.4,4,4.3,4,3.8,3.8,3.8,4,4,3.8,3.8,4.3,5.3,4.5,4.8,5.4,5.6,6.1,6.4,6.9,7.1,7.4,7.4,7.6,7.6,7.6,7.6,7.8,7.6,7.8,7.8,7.6,7.6,7.8,7.8,7.9,7.8,7.6,7.6,7.6,7.4,7.4,7.3,7.3,7.3,7.2,7,6.7,6.5,6.2,6,5.6,5.3,4.2,3.4,2.7,1.8,0.4,0,0,0.8,0.4,0.4,3.1,0,0.8,0.4,0.4,1.8,2.2,2.7,3.1,3.3,3.3,3.3,3.3,3.3,3.4,3.3,3.4,3.4,3.4,3.4],[null],[null],[null],[null]]}

How's that?

Regds
Ed

Edit: this is the completed chart of the above data:

Right... Had a look at your awesome chart you made...Brilliant!!

Found where you are extracting the data, under the cursor:

const value = this.data.datasets[datasetIndex].data[index]
and changed it to:
const value = this.data.labels[index]

This then sends the frequency under the cursor which I would then use to "auto adjust" the tuner for further audio investigation...

At the moment though, the ?datasets array? and getting it from the array I have available is still beyond me, but I will keep trying!!

Thanks heaps!!

Ed

1 Like

image

also why is the first element of the labels null and the element of each data sub array null ?
There are still some issues with the data. Maybe the ui_chart is forgiving with the stucture of the data but for the ui_template example it doesnt render.

Nice :wink:

Hi Andy,

That is the "undefined" start of the chart... The actual frequency/sig strength data starts at the next point of the scan... it serves as a break between extended scans when they are concatenated on the x axis

I had to have this in for the chart to "build" its image as the scan progressed from one to the next scan cycle, ran into "array" problems that I had to parrot from elsewhere, if I understand it correctly, I had to have a start point for each ?data subset? to push the subsequent data onto... (Forgive me if I'm not using the correct terms)...

One of the strengths of the way I am doing it is that it allows me to see the progressive building of the chart as the data comes in, not just after all of the data has been collected...

Pray tell, on a single series of data, if data is streamed in, does it render the chart progressively, or wait until all data is present?

Ed

it could render the data progressively .. why not .. with some modification in the code .. you can push data to labels (x-axis) and datasets data (y-axis) .. but the data may not be in order (because im using Math.random()) as you see with the example below .. and you need to implement some if conditions to remove old data because the chart can get crowded pretty fast.

[{"id":"56c4a2cd26f09c53","type":"ui_template","z":"5847b7aa62131d37","group":"15350e70812fdbaf","name":"","order":0,"width":"24","height":"20","format":"<div class=\"chart-container\" style=\"position: relative; height:40vh; width:50vw\">\n    <canvas id=\"myChart\"></canvas>\n</div>\n\n<script>\n    (function(scope) {\n\nvar ctx = document.getElementById('myChart').getContext('2d');\nvar myChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        labels: [],\n        datasets: [{\n            label: \"Series1\",\n            data: [],\n            fill: false,\n            borderColor: 'rgba(255, 206, 86, 0.2)',\n            tension: 0.1\n            }]\n    },\n    options: {\n    //    scales: {\n    //   x: {display: true,},\n    //   y: {display: true,type: 'logarithmic' }\n    // },\n        responsive: true,\n\n        onClick(e) {\n            const activePoints = this.getElementAtEvent(e)[0];\n            var index = activePoints._index;\n            var datasetIndex = activePoints._datasetIndex;\n            const value = this.data.datasets[datasetIndex].data[index]\n\n            scope.send({ payload:  value })\n        }\n    },\n\n});\n\n\n// watch for msg\nscope.$watch('msg', (msg) => {\n   if (msg) {\n      // Do something when msg arrives\n    //myChart.clear();\n    myChart.data.labels.push(msg.payload.label)   // add x-axis label\n    myChart.data.datasets[0].data.push(msg.payload.data)  // add y-axis value\n    //console.log(myChart.data)\n    myChart.update();\n   \n\n    }\n})\n\n\n})(scope)\n\n</script>","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","className":"","x":760,"y":520,"wires":[["4fed8d054d6da28e"]]},{"id":"4fed8d054d6da28e","type":"debug","z":"5847b7aa62131d37","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":520,"wires":[]},{"id":"3cf4d6690ab2b6a4","type":"debug","z":"5847b7aa62131d37","name":"2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":440,"wires":[]},{"id":"6f32211c8acf69c9","type":"inject","z":"5847b7aa62131d37","name":"trigger","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":420,"y":520,"wires":[["6a847ae1f209df0d"]]},{"id":"6a847ae1f209df0d","type":"function","z":"5847b7aa62131d37","name":"Chart Data","func":"msg.payload = {\n    label: (9 + Math.random() * 2).toFixed(2) + ' Hz',  // random x-axis\n    data: 2 + Math.random() * 2    // random y-axis\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":580,"y":520,"wires":[["56c4a2cd26f09c53","3cf4d6690ab2b6a4"]]},{"id":"15350e70812fdbaf","type":"ui_group","name":"Chart JS","tab":"39a6d442788cfb84","order":1,"disp":true,"width":"24","collapse":false,"className":""},{"id":"39a6d442788cfb84","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

Thats pretty good... and with few enough "higher grade" bits that I can follow in the function block!!

msg.payload = {
    label: (9 + Math.random() * 2).toFixed(2) + ' Hz',  // random x-axis
    data: 2 + Math.random() * 2    // random y-axis
};

return msg;

This is going to be a dumb question, but how would you add a second series to the above?

I follow the label/data bits... I think...

your previous function example was just a little too "higher grade" for me...

Ed

that's what the example in my first post do .. dynamically add complete dataseries

Check out the ChartJs example website
They have a few examples of how they add complete datasets .. or add data points to existing (already defined datasets)

If you click the Setup tab in the code section you see that they start with 2 x Datasets with already defined properties like backgroundColor etc ..
also check in the Actions tab you see code example of how to push that data to the appropriate datasets

Apologies... I'm just too dumb to understand I guess....

This I can grasp:

    label: (9 + Math.random() * 2).toFixed(2) + ' Hz',  // random x-axis
    data: 2 + Math.random() * 2 ,   // random y-axis
};

This I can follow in "spirit" :

datasets: [
    {
      label: 'Dataset 1',
      data: Utils.numbers(NUMBER_CFG),
      borderColor: Utils.CHART_COLORS.red,
      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
    },
    {
      label: 'Dataset 2',
      data: Utils.numbers(NUMBER_CFG),
      borderColor: Utils.CHART_COLORS.blue,
      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
    }
  ]

as to what it is doing, but I can't ?translate? it into the little bit at the top of this post that I can grasp...

As to the Actions section you have referred me to, again, I can see what they do, but only because they have a clear name (ie:name: 'Add Dataset',) attached to each section... As to how it does it... Dark Magic....

Ed

Here's a more clean example of how you could prepare your data in a Function node and receive it at the front-end to update a 2 X dataset line chart.

[{"id":"d58b635f1bfdaa7f","type":"ui_template","z":"5847b7aa62131d37","group":"6efcc19883dcdf68","name":"","order":0,"width":"24","height":"20","format":"<div class=\"chart-container\" style=\"position: relative; height:40vh; width:50vw\">\n    <canvas id=\"myChart\"></canvas>\n</div>\n\n<script>\n    (function(scope) {\n\nvar ctx = document.getElementById('myChart').getContext('2d');\nvar myChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n        datasets: [\n    {\n        label: 'Dataset 1',\n        data: [],\n        borderColor: 'red',\n        backgroundColor:'red',\n        fill: false\n    },\n    {\n        label: 'Dataset 2',\n        data: [],\n        borderColor: 'blue',\n        backgroundColor: 'blue',\n        fill: false\n    }\n    ]\n    },\n    options: {\n        scales: {\n            y: {\n                beginAtZero: true\n            }\n        },\n        responsive: true,\n\n        onClick(e) {\n            const activePoints = this.getElementAtEvent(e)[0];\n            var index = activePoints._index;\n            var datasetIndex = activePoints._datasetIndex;\n            const value = this.data.datasets[datasetIndex].data[index]\n\n            scope.send({ payload:  value })\n        }\n    },\n    \n  \n});\n\n// watch for msg\nscope.$watch('msg', (msg) => {\n   if (msg) {\n    // Do something when msg arrives\n    myChart.clear();\n    myChart.data.datasets[0].data = [...msg.payload.data0];\n    myChart.data.datasets[1].data = [...msg.payload.data1];\n    myChart.update();\n    }\n})\n\n\n})(scope)\n\n</script>","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","className":"","x":740,"y":540,"wires":[["d1850c4a1e48066e"]]},{"id":"53e059241791ed04","type":"inject","z":"5847b7aa62131d37","name":"trigger","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":260,"y":540,"wires":[["17c879d6076d6411"]]},{"id":"17c879d6076d6411","type":"function","z":"5847b7aa62131d37","name":"Chart Data","func":"msg.payload = {\n    \n    data0: [\n        12 + Math.random() * 2,\n        19 + Math.random() * 2,\n        3 + Math.random() * 2,\n        5 + Math.random() * 2,\n        7 + Math.random() * 2,\n        3 + Math.random() * 2,\n        4 + Math.random() * 2\n    ],\n    data1: [\n        3 + Math.random() * 2,\n        9 + Math.random() * 2,\n        3 + Math.random() * 2,\n        5 + Math.random() * 2,\n        2 + Math.random() * 2,\n        7 + Math.random() * 2,\n        3 + Math.random() * 2\n    ]\n\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":540,"wires":[["d58b635f1bfdaa7f","0869cd746de10613"]]},{"id":"0869cd746de10613","type":"debug","z":"5847b7aa62131d37","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":620,"y":440,"wires":[]},{"id":"d1850c4a1e48066e","type":"debug","z":"5847b7aa62131d37","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":540,"wires":[]},{"id":"6efcc19883dcdf68","type":"ui_group","name":"Chart JS","tab":"39a6d442788cfb84","order":1,"disp":true,"width":"24","collapse":false,"className":""},{"id":"39a6d442788cfb84","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

in this example the labels are also fixed (hardcoded) .. in your case, from what i understand, the labels also change depending on the selected freq. so you need to figure out how to send them also in the msg and update the chart.

Thanks for the latest example... Question:

[...msg.payload.data1]

the three ... before msg? Haven't seen that before....

Ed