Chart - Multiple data overlaying

Hi All,

I am trying to find a way to overlay additional data on an existing chart - is it possible?

Not knowing exactly how to ask the question, I think it may be best to give an example..

I am scanning a variable range of radio frequencies and charting the frequency and signal level recieved for each.

At the end of the scan, I want to rescan the same set of frequencies and have them added as a separate series over the previously, now charted, series... possibly 3 to 4, maybe 5 times over...

I am aware of the memory overheads and performance problems associated with large quantities of data in charts and am willing to accept those risks...

I have kludged together a mishmash chart that works fine for a single series, but lack the knowledge of array manipulation to accomplish this task fully...

Can anybody perhaps help?

TIA
Ed

I think that you can send additional series to the chart node. I don't often use it myself so I can't really remember. Maybe the docs have the answer?

Hello ..
and how often is the chart updated with new values ?

every time you read your frequency and signal strength you can push the data to a Flow Context array
using a Function node.

when the data array length reaches 5 you .shift() the first element (which is the oldest data) add the new data and resend it to the chart.

if you paste a few rows of sample data maybe we can help a bit with code

Thanks for coming back to me!!

The chart would be updated up to a max of about 4 or 5 times, each scan run consecutively after... I could pass a message across to the "Append data" function when the scan restarts.

Whoa! Way above my fireplace!! (For me, remembering to put a spoonful of coffee into my morning coffee is sometimes a bit complex...LOL

Here is the section that deals with the chart in question:

[
    {
        "id": "384dbcfa79f8f3e0",
        "type": "group",
        "z": "68e5c71acc43e814",
        "name": "Freq vs S Graphing",
        "style": {
            "label": true
        },
        "nodes": [
            "6e2fc0aad76d2ddb",
            "f4ef149d3570d66b",
            "12fd87666ba89d9f",
            "2fd5390aeaa82f47",
            "ae713c2feb2dbfb9",
            "937687616f56fc57",
            "d09adadfe47dbca6",
            "35351d98e237075b",
            "3aa1eadea0116fce"
        ],
        "x": 94,
        "y": 2019,
        "w": 752,
        "h": 162
    },
    {
        "id": "6e2fc0aad76d2ddb",
        "type": "function",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "Append Data",
        "func": "freqsigctl = flow.get('freqsigctl')\nsweepenabled = flow.get('sweepenabled')\n\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] = [];\n    m.data[1] = [];\n    //\n    m.labels.push(0)//frequency\n    m.series.push('S','GHz');//S Meter Label\n    m.data[0].push(0);//S meter Reading\n    m.data[1].push(0);\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\n\nfrequency = msg.payload.frequency\nsigstrength = msg.payload.sigstrength\nm=context.get(\"m\")\n/*if (m === undefined){\n    //node.warn(\"m\");\n    m={};\n    m.labels= []\n    m.series = [];\n    m.data = [];\n    m.data[0] = [];\n    m.data[1] = [];\n}*/\n//node.warn(\"m2\");\nm.labels.push(frequency)//frequency\nm.series.push('S','GHz');//S Meter Label\nm.data[0].push(sigstrength);//S meter Reading\nm.data[1].push(frequency/1000);\n\n\ncontext.set(\"m\",m)\nreturn {payload:[m]};\n\n/*[{\n    \"series\": [\"X\", \"Y\", \"Z\" ],\n    \"data\": [ [5,6,9,10], [3,8,5,10], [6,7,2,10] ],\n    \"labels\": [ \"Jan\", \"Feb\", \"Mar\", \"Apr\" ]\n}]*/\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 590,
        "y": 2100,
        "wires": [
            [
                "f4ef149d3570d66b"
            ]
        ]
    },
    {
        "id": "f4ef149d3570d66b",
        "type": "ui_chart",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "",
        "group": "62350f50d4b09ff8",
        "order": 48,
        "width": 24,
        "height": 4,
        "label": "F(MHz) / S",
        "chartType": "line",
        "legend": "false",
        "xformat": "auto",
        "interpolate": "bezier",
        "nodata": "No Data",
        "dot": false,
        "ymin": "0",
        "ymax": "10",
        "removeOlder": 1,
        "removeOlderPoints": "2000",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 750,
        "y": 2100,
        "wires": [
            []
        ]
    },
    {
        "id": "12fd87666ba89d9f",
        "type": "link in",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "S",
        "links": [
            "b6d2e58a455bebca"
        ],
        "x": 135,
        "y": 2060,
        "wires": [
            [
                "937687616f56fc57"
            ]
        ]
    },
    {
        "id": "2fd5390aeaa82f47",
        "type": "link in",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "F",
        "links": [
            "f9de592305b482ce"
        ],
        "x": 135,
        "y": 2100,
        "wires": [
            [
                "ae713c2feb2dbfb9"
            ]
        ]
    },
    {
        "id": "ae713c2feb2dbfb9",
        "type": "function",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "frequency",
        "func": "msg.topic = \"frequency\"\nmsg.payload=msg.payload * 1000\nmsg.payload = parseInt(msg.payload*1000000)\nmsg.payload = msg.payload/1000000\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 240,
        "y": 2100,
        "wires": [
            [
                "d09adadfe47dbca6"
            ]
        ]
    },
    {
        "id": "937687616f56fc57",
        "type": "function",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "sigstrength",
        "func": "msg.topic = \"sigstrength\"\nmsg.payload = parseInt(msg.payload*100);\nmsg.payload = msg.payload/100;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 250,
        "y": 2060,
        "wires": [
            [
                "d09adadfe47dbca6"
            ]
        ]
    },
    {
        "id": "d09adadfe47dbca6",
        "type": "join",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "",
        "mode": "custom",
        "build": "object",
        "property": "payload",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "accumulate": false,
        "timeout": ".2",
        "count": "2",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "num",
        "reduceFixup": "",
        "x": 410,
        "y": 2100,
        "wires": [
            [
                "6e2fc0aad76d2ddb"
            ]
        ]
    },
    {
        "id": "35351d98e237075b",
        "type": "ui_multistate_switch",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "Auto/Run/Pause/Clr",
        "group": "62350f50d4b09ff8",
        "order": 47,
        "width": 12,
        "height": 1,
        "label": "Freq/S Chart:",
        "stateField": "payload",
        "enableField": "enable",
        "passthroughField": "passthrough",
        "inputMsgField": "inputmsg",
        "rounded": true,
        "useThemeColors": false,
        "hideSelectedLabel": false,
        "multilineLabel": false,
        "passThrough": "never",
        "inputMsg": "all",
        "userInput": "enabled_show",
        "options": [
            {
                "label": "Auto",
                "value": "Auto",
                "valueType": "str",
                "color": "#0000ff"
            },
            {
                "label": "Run",
                "value": "Run",
                "valueType": "str",
                "color": "#009933"
            },
            {
                "label": "Pause",
                "value": "Pause",
                "valueType": "str",
                "color": "#ffc0cb"
            },
            {
                "label": "Clear",
                "value": "Clear",
                "valueType": "str",
                "color": "#ff6666"
            }
        ],
        "x": 220,
        "y": 2140,
        "wires": [
            [
                "3aa1eadea0116fce"
            ]
        ]
    },
    {
        "id": "3aa1eadea0116fce",
        "type": "function",
        "z": "68e5c71acc43e814",
        "g": "384dbcfa79f8f3e0",
        "name": "Decipher Run / Pause set freqsigctl",
        "func": "if(msg.payload== 'Auto'){\n    flow.set('freqsigctl','auto')\n    return ;\n\n}if(msg.payload== 'Run'){\n    flow.set('freqsigctl','run')\n    return ;\n}\nif(msg.payload== 'Pause'){\n    flow.set('freqsigctl','pause')\n    return ;\n}\nif(msg.payload== 'Clear'){\n    flow.set('freqsigctl','run')\n    node.send(msg);//send Clear Instruction\n    msg.topic = 'clear';\n    msg.payload= \"Pause\";//For visual on switch\n    node.send(msg);\n    flow.set('freqsigctl','pause')\n    return msg;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 480,
        "y": 2140,
        "wires": [
            [
                "6e2fc0aad76d2ddb",
                "35351d98e237075b"
            ]
        ]
    },
    {
        "id": "62350f50d4b09ff8",
        "type": "ui_group",
        "name": "PCR1000",
        "tab": "554e77d51ad13f2d",
        "order": 1,
        "disp": false,
        "width": "24",
        "collapse": false,
        "className": ""
    },
    {
        "id": "554e77d51ad13f2d",
        "type": "ui_tab",
        "name": "Basic",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

the two links feed in a) a frequency in hz and b)the "S" reading associated with it....

and here is a chart produced by it:

A second, third and fourth overlaid trace would help me to visually differentiate between short term disruptions and actual signals...

No further data storage other than present info is required....

Regds
Ed

Maybe you are looking for option to display range of data deviation, something like this?

Definitely takes to use the raw chartjs library with ui_template. And most probably the library needed to be included with higher version the dashboard has by default.
Or with whatever JS charting library which has support for such kind of effects...

Very nice!!

but my needs are not even that complex... simply the 3 source traces would be sufficient in my case...

Regds
E

For single line I'd try with d3.js

And on the subject of the trace... is there any way to read the information that pops up when you hover your cursor over the trace and capture it with maybe a left click?

image

That would be a lovely feature to quick tune my receiver!!

Ed

With raw charjs library I think you probably can use getElementsAtEvent(event) method but I never tried.

https://www.chartjs.org/docs/2.6.0/developers/api.html#getelementatevente

Brain meltdown..... Reaching for beer....

Yeah. Take a beer, and break, and then just examine some examples SteppedLine Chart together with Line Chart - #17 by hotNipi how to use chartjs with ui_template node and try to achieve your goal.
Then most probably many questions will rise up but they will be sharp on target so they will be easier to answer.
:beers:

1 Like

A few beers later.....

Got my thoughts in a row...(Sort Of...)

If I understand it correctly:

The chart is built from an array, as the scanning data for the initial traces come in, the following extends the array:

m=context.get("m")
m.labels.push(frequency)//frequency
m.series.push('GHz','S');//S Meter Label
m.data[0].push(frequency/1000);//Frequency ghz
m.data[1].push(sigstrength);//S meter Reading 1
context.set("m",m)
return {payload:[m]};

After each "packet" of info is rec'd, the array is incremented with another set of elements and the chart updates with another point....

Now, after the first scan is complete, all is good. the chart is complete....

At this point, the scanner starts again at the beginning and starts passing more data forward again... Currently, this appends to the array and causes the chart to get "longer", ie the array gets more elements....

What I would like to do at this point is instead, to go back to the start of the array and insert another trace, different colour, over the existing trace.....

I can supply a "restarting" signal to signify the trace is restarting, ie another dimension has to be added to the array, but I don't know how to do the actual redimensioning/expansion of the array or whether the chart will actually start adding the other trace or maybe ?blank out? until the next scan is finished and the new element in the array has the same number of data points as the existing data...

That's about as clear as I can muddle through it with my daft explanation at the moment.... I feel another "Beerlapse of understanding" coming on.....

There will be issues with differences in frequencies between the 2 scans..... This I can cover later...

Now, my thinking is.... Do I instead create the array from the get go with the additional dimensions to allow for multiple scans (say by adding: m.data[2].push(sigstrength);//S meter Reading 2 ?? .....eek... falling down the rabbit hole again.... Beer transfusion imminent...

Regds
Ed

I took an old example and tried to modify as well I understood your case.
May be it helps.

[
    {
        "id": "26d66b9b.186034",
        "type": "ui_template",
        "z": "e490bfdba10fb48a",
        "group": "304730.1de8e8d",
        "name": "Line Chart",
        "order": 4,
        "width": "15",
        "height": "10",
        "format": "",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": false,
        "templateScope": "local",
        "className": "",
        "x": 670,
        "y": 290,
        "wires": [
            []
        ]
    },
    {
        "id": "29e44bca.6d2594",
        "type": "inject",
        "z": "e490bfdba10fb48a",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": ".3",
        "crontab": "",
        "once": false,
        "onceDelay": "0.62",
        "topic": "",
        "payloadType": "date",
        "x": 310,
        "y": 260,
        "wires": [
            [
                "98fd0130.0550d"
            ]
        ]
    },
    {
        "id": "98fd0130.0550d",
        "type": "function",
        "z": "e490bfdba10fb48a",
        "name": "fake live data",
        "func": "var scanid = context.get(\"scanid\") || 0;\nvar idx = context.get(\"idx\") || 0;\nvar labels = ['first', 'second', 'third', 'fourth']\n\nmsg.payload = {}\nmsg.payload.y = Math.random() * 100\nmsg.payload.x = idx\nmsg.topic = labels[scanid]\n\nidx ++\nif(idx > 19){\n    idx = 0\n    scanid ++\n\n}\ncontext.set(\"idx\", idx);\nif(scanid > 3){\n    scanid = 0\n}\ncontext.set(\"scanid\", scanid);\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 470,
        "y": 260,
        "wires": [
            [
                "26d66b9b.186034"
            ]
        ]
    },
    {
        "id": "55e9c5a5.e31f0c",
        "type": "template",
        "z": "e490bfdba10fb48a",
        "name": "",
        "field": "template",
        "fieldType": "msg",
        "format": "html",
        "syntax": "mustache",
        "template": "<canvas id=\"myChart\" width=600 height =300></canvas>\n<script>\nvar textcolor = getComputedStyle(document.documentElement).getPropertyValue('--nr-dashboard-widgetTextColor');\nvar gridcolor = getComputedStyle(document.documentElement).getPropertyValue('--nr-dashboard-groupBorderColor');\nvar linecolors = ['#009900','#889900','#ff0000','#B000ff']\nvar labels = ['first','second','third','fourth']\n\nfunction createDataset(idx){\n    return {\n        label: labels[idx],\n        backgroundColor: linecolors[idx],\n        borderColor: linecolors[idx],\n        data: [],\n        yAxisID: 'left-y-axis',\n        steppedLine: false,\n        fill: false,\n        borderWidth: 1\n    }\n}\n\nvar ctx = document.getElementById('myChart').getContext('2d');\nvar chart = new Chart(ctx, {\n    // The type of chart we want to create\n    type: 'line',\n\n    // The data for our dataset\n    data: {\n        labels: labels,\n        datasets: [\n            createDataset(0),createDataset(1),createDataset(2),createDataset(3),\n        ]\n    },\n\n    // Configuration options go here\n    options: {\n        elements:{\n            point:{\n                radius:0\n            }\n        },\n        scales: {\n            yAxes: [\n                {\n                    gridLines :{display:false},\n                    id: 'left-y-axis',\n                    type: 'linear',\n                    position: 'left',\n                    ticks: {\n                        fontColor: linecolors[0]\n                    }\n                }\n            ],\n            xAxes: [\n                {\n                    gridLines :{zeroLineColor:gridcolor,color:gridcolor,lineWidth:0.5},\n                type: 'linear',\n                id: 'custom-x-axis',\n                position:'bottom',\n                ticks: {\n                    fontColor:\"#ccc\",\n                    max: 20,\n                    min: 0,\n                    stepSize: 1\n                    }\n                }\n            ]\n        }\n    }\n});\nfunction addData(chart, data, label) {\n    chart.data.datasets.forEach((dataset) => {\n       \n        if(dataset.label == label){\n            if(data.x == 0){\n                //first occurence of data for series, clear that series\n                dataset.data = []\n            }\n            dataset.data.push(data);           \n        }        \n    });\n    chart.update(0);//0 means no animation\n}\n(function(scope) {\n  scope.$watch('msg', function(msg) {\n    if (msg) {\n      // Do something when msg arrives\n      addData(chart,{x:msg.payload.x,y:msg.payload.y},msg.topic)    \n\n    }\n  });\n})(scope);\n</script>\n",
        "output": "str",
        "x": 460,
        "y": 320,
        "wires": [
            [
                "26d66b9b.186034"
            ]
        ]
    },
    {
        "id": "bf7ecf9e.8f61f",
        "type": "inject",
        "z": "e490bfdba10fb48a",
        "name": "",
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 310,
        "y": 320,
        "wires": [
            [
                "55e9c5a5.e31f0c"
            ]
        ]
    },
    {
        "id": "304730.1de8e8d",
        "type": "ui_group",
        "name": "Chart",
        "tab": "ebf9c239.14c15",
        "order": 3,
        "disp": true,
        "width": "15",
        "collapse": false
    },
    {
        "id": "ebf9c239.14c15",
        "type": "ui_tab",
        "name": "Ovens",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    }
]

Hi H!

I forego'd my beerbreak... and might have come up with a solution this side too....

What do you thunk of this... (Actual live scan of 4.9 to 5Mhz as of a few minutes ago):

I are almost able to done this stuff!!

(After a couple of hours of tail chasing and plugging code in at random... Who was it that said "If you give a 100 monkeys each a typewriter, and have them bang at it for a 100 years, you will get the complete works of Shakespeare" .... Well I am one of those monkeys!!)

Regds
Ed

Everyone who writes JavaScript is one of those monkey's :crazy_face: :rofl:

2 Likes

Hey H....

...Wow....

Ed

1 Like