Chart Variable Timeline X-Axis

Hello,

I am wondering if anybody can help me find a solution to which allows me to dynamically change the length of time shown on a chart node? For example, I can change the y-axis min and max values with ui_control, I'd like to be able to do the same with the x-axis, so that I can dynamically adjust if I want to see the trend of the last hour or just the last 5 minutes. This could possibly also be a zoom function. Alternatively, It could switch between displaying a fixed amount of time and being able to put in a start or stop time.

Thanks,
Russell

X-axis can be adjusted via msg.ui_control.options. By reading your requirements I can't really give strong advise but you probably need to do a bunch of experiments. See this thread to find examples and maybe to get things going Help to Change X- Axis Value
And here is the link to the docs of charts https://www.chartjs.org/docs/latest/charts/line.html

Thanks for the tips. I'm familiar with using ui_control to change ui properties, but do you know what the command/ option name would be for the X-axis?

There is many many things to adjust and many ways to achieve different layouts so it is kind of impossible to point to some property. To get things going, make small example flow with your data and let's then start to modify things according to what is possible via https://www.chartjs.org/docs/latest/axes/

Here is part of the flow:

[{"id":"ded35285.4448c","type":"comment","z":"6e9cad82.823624","name":"Source","info":"Be it serial or any other data source. ","x":520.7022018432617,"y":571.108320236206,"wires":[]},{"id":"4c5b0ed0.3f24","type":"ui_gauge","z":"6e9cad82.823624","name":"","group":"ae0413d2.d9b4d","order":1,"width":"0","height":"0","gtype":"gage","title":"Melt Temperature","label":"°F","format":"{{value}}","min":0,"max":"1000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"700","seg2":"900","x":1463.5187606811523,"y":607.1264162063599,"wires":[]},{"id":"7ac13877.dafdb8","type":"ui_chart","z":"6e9cad82.823624","name":"","group":"ef860366.cd032","order":1,"width":0,"height":0,"label":"Melt Temperature","chartType":"line","legend":"false","xformat":"auto","interpolate":"linear","nodata":"awaiting data...","dot":false,"ymin":"0","ymax":"1000","removeOlder":"30","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":true,"outputs":1,"x":1460.5192489624023,"y":570.1264801025391,"wires":[[]]},{"id":"bd76261.6cd22d8","type":"function","z":"6e9cad82.823624","name":"Format Y-Axis","func":"//to change ymax\nif (msg.topic === \"0\") {\n    var max= msg.payload;\n    delete msg.payload;\n    delete msg.topic;\n    msg.ui_control = { \"ymax\":(max) }\n    return msg;\n}\n//to change ymin\nif (msg.topic === \"1\") {\n    var min= msg.payload;\n    delete msg.payload;\n    delete msg.topic;\n    msg.ui_control = { \"ymin\":(min) }\n    return msg;\n}\n\n \nreturn msg;","outputs":1,"noerr":0,"x":1229.4373397827148,"y":571.049952507019,"wires":[["7ac13877.dafdb8","de972e80.9b3af"]]},{"id":"9a0df301.f6586","type":"ui_text_input","z":"6e9cad82.823624","name":"","label":"Ymax","tooltip":"","group":"881ee563.ffb7f8","order":2,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"0","x":1001.4374923706055,"y":471.04994201660156,"wires":[["bd76261.6cd22d8"]]},{"id":"6d583d8e.853674","type":"ui_text_input","z":"6e9cad82.823624","name":"","label":"Ymin","tooltip":"","group":"881ee563.ffb7f8","order":3,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"1","x":1003.1374359130859,"y":513.3831939697266,"wires":[["bd76261.6cd22d8"]]},{"id":"9d084fca.7d4e8","type":"function","z":"6e9cad82.823624","name":"extract desired data","func":"msg.payload = msg.payload.data2;\nreturn msg;","outputs":"1","noerr":0,"x":991.2187194824219,"y":608.0248775482178,"wires":[["bd76261.6cd22d8","a200c11d.69471","4c5b0ed0.3f24"]]},{"id":"f23359d4.eb63b8","type":"debug","z":"6e9cad82.823624","name":"","active":true,"console":"false","complete":"true","x":867.0187377929688,"y":668.0249633789062,"wires":[]},{"id":"7132691f.650ce8","type":"function","z":"6e9cad82.823624","name":"Data to be graphed","func":"var Test = msg.payload;\nmsg.payload = {}; //null msg obj and create new properties\n\nmsg.payload.data0 = \"other value 1\";\nmsg.payload.data1 = \"other value 2\";\nmsg.payload.data2 = Test;\n\nreturn msg;","outputs":1,"noerr":0,"x":678.5186347961426,"y":610.1312007904053,"wires":[["f23359d4.eb63b8","9d084fca.7d4e8"]]},{"id":"8efab9ad.031128","type":"inject","z":"6e9cad82.823624","name":"","topic":"","payload":"","payloadType":"str","repeat":"5","crontab":"","once":false,"x":333.9249744415283,"y":609.512487411499,"wires":[["76fb76fa.5218d8"]]},{"id":"76fb76fa.5218d8","type":"random","z":"6e9cad82.823624","name":"","low":"1","high":"500","inte":"false","property":"payload","x":491.5124816894531,"y":610.1687393188477,"wires":[["7132691f.650ce8"]]},{"id":"a200c11d.69471","type":"debug","z":"6e9cad82.823624","name":"","active":true,"console":"false","complete":"true","x":1171.0188064575195,"y":652.0249099731445,"wires":[]},{"id":"de972e80.9b3af","type":"debug","z":"6e9cad82.823624","name":"","active":true,"console":"false","complete":"true","x":1414.018798828125,"y":494.0249938964844,"wires":[]},{"id":"ae0413d2.d9b4d","type":"ui_group","z":"","name":"Dashboard","tab":"311587f6.ef07b8","order":1,"disp":true,"width":"4","collapse":false},{"id":"ef860366.cd032","type":"ui_group","z":"","name":"Charts","tab":"311587f6.ef07b8","order":3,"disp":true,"width":"6"},{"id":"881ee563.ffb7f8","type":"ui_group","z":"","name":"Chart Scaling","tab":"311587f6.ef07b8","order":2,"disp":true,"width":"3","collapse":true},{"id":"311587f6.ef07b8","type":"ui_tab","z":"","name":"DS Pilot Extrusion Line","icon":"dashboard","order":2}]
1 Like

Well there is too much of it. To keep things concentrated it takes only sample of data structure and nodes dedicated to modifications and to display the chart.

Good point. I've revised it and made it pretty short now. I'm quite new to Node Red, so my code is surely far from optimized, sorry.

So x-axis and amount of data to display (time wise)
Basically it takes to tell to chart starting point of time in milliseconds. Chart takes this value in property time.min
It is current moment minus your desired time (all in milliseconds)
Here is addition to your flow

[{"id":"7cab122e.784c2c","type":"function","z":"cefda5c5.afe5c8","name":"Format X-Axis","func":"if(msg.topic == 'time'){\n    var x = msg.payload * 60000; //input convert to milliseconds\n    var d = new Date().getTime() - x //current time in milliseconds - input\n    delete msg.payload;\n    delete msg.topic;\n    msg.ui_control = { \n        options: {\n            scales: {\n                xAxes: [{\n                    type: 'time',\n                    time: {\n                        min: d\n                    }\n                }]\n            }\n        }\n    }\n    return msg; \n}\n\n \nreturn msg;","outputs":1,"noerr":0,"x":450,"y":200,"wires":[["2992be42.4bb6b2"]]},{"id":"73f1e3be.f64f8c","type":"ui_text_input","z":"cefda5c5.afe5c8","name":"","label":"Minutes","tooltip":"","group":"480f6c25.dd7064","order":3,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"time","x":280,"y":200,"wires":[["7cab122e.784c2c"]]},{"id":"480f6c25.dd7064","type":"ui_group","z":"","name":"Chart Scaling","tab":"20d4bd59.a21a22","order":2,"disp":true,"width":"3","collapse":true},{"id":"20d4bd59.a21a22","type":"ui_tab","z":"","name":"DS Pilot Extrusion Line","icon":"dashboard","order":2}]

Document source https://www.chartjs.org/docs/latest/axes/cartesian/time.html

2 Likes

Hey, great work! That's exactly what I was looking for, Thanks!

Yeah. But don't rush. Soon you will notice that it needs more adjustments.
So I'm preparing a bit more completed solution for this as it might be useful for others also.
Takes a little time

1 Like

So here it is.

[{"id":"cefda5c5.afe5c8","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"70466254.88d96c","type":"ui_gauge","z":"cefda5c5.afe5c8","name":"","group":"9e271ef8.1ed8","order":1,"width":"0","height":"0","gtype":"gage","title":"Melt Temperature","label":"°F","format":"{{value|number:2}}","min":0,"max":"1000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"700","seg2":"900","x":690,"y":430,"wires":[]},{"id":"2992be42.4bb6b2","type":"ui_chart","z":"cefda5c5.afe5c8","name":"","group":"c4bae040.b8a2","order":1,"width":0,"height":0,"label":"Melt Temperature","chartType":"line","legend":"false","xformat":"auto","interpolate":"linear","nodata":"awaiting data...","dot":false,"ymin":"0","ymax":"1000","removeOlder":"30","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":true,"outputs":1,"x":690,"y":360,"wires":[[]]},{"id":"7dda56d7.a24c88","type":"ui_text_input","z":"cefda5c5.afe5c8","name":"","label":"Ymax","tooltip":"","group":"480f6c25.dd7064","order":2,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"ymax","x":210,"y":250,"wires":[["3a327b6f.dbe7a4"]]},{"id":"ab589fd3.08d9","type":"ui_text_input","z":"cefda5c5.afe5c8","name":"","label":"Ymin","tooltip":"","group":"480f6c25.dd7064","order":3,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"ymin","x":210,"y":210,"wires":[["3a327b6f.dbe7a4"]]},{"id":"52ff4ae1.a7cac4","type":"inject","z":"cefda5c5.afe5c8","name":"","topic":"","payload":"","payloadType":"str","repeat":"5","crontab":"","once":false,"onceDelay":"","x":180,"y":400,"wires":[["1f6bed5c.fafa93"]]},{"id":"1f6bed5c.fafa93","type":"random","z":"cefda5c5.afe5c8","name":"random between 1 - 500","low":"1","high":"500","inte":"false","property":"payload","x":370,"y":400,"wires":[["2992be42.4bb6b2","70466254.88d96c"]]},{"id":"7cab122e.784c2c","type":"function","z":"cefda5c5.afe5c8","name":"Format chart","func":"\nvar x = flow.get('chartprops').xaxis.min; //time back in milliseconds\nvar d = new Date().getTime() - x //current time in milliseconds - desired time\ndelete msg.payload;\ndelete msg.topic;\n\n//send all modification in one go\nmsg.ui_control = { \n    options: {\n        scales: {\n            xAxes: [{\n                type: 'time',\n                time: {\n                    min: d\n                }\n            }],\n            yAxes: [{\n            ticks: {\n                suggestedMin: flow.get('chartprops').yaxis.min,\n                suggestedMax: flow.get('chartprops').yaxis.max\n            }\n        }]\n        }\n    }\n}\nreturn msg; \n\n","outputs":1,"noerr":0,"x":470,"y":340,"wires":[["2992be42.4bb6b2"]]},{"id":"73f1e3be.f64f8c","type":"ui_text_input","z":"cefda5c5.afe5c8","name":"","label":"Minutes","tooltip":"","group":"480f6c25.dd7064","order":3,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"time","x":200,"y":170,"wires":[["3a327b6f.dbe7a4"]]},{"id":"af03cda2.7215e","type":"inject","z":"cefda5c5.afe5c8","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":240,"y":120,"wires":[["81cc7000.25c7b"]]},{"id":"81cc7000.25c7b","type":"function","z":"cefda5c5.afe5c8","name":"init chart options in flow","func":"var chartprops = flow.get('chartprops') || {}\n// set same values as in chart configuration\nchartprops.xaxis = {min:3600000}//time frame\nchartprops.yaxis = {min:0,max:1000}//vertical axis limits\nflow.set('chartprops',chartprops)\n","outputs":1,"noerr":0,"x":460,"y":120,"wires":[[]]},{"id":"3a327b6f.dbe7a4","type":"function","z":"cefda5c5.afe5c8","name":"Store chart options","func":"var chartprops = flow.get('chartprops')\nswitch(msg.topic){\n    case 'time':{\n        chartprops.xaxis.min = msg.payload * 60000\n        break\n    }\n    case 'ymin':{\n        chartprops.yaxis.min = msg.payload\n        break\n    }\n    case 'ymax':{\n        chartprops.yaxis.max = msg.payload\n        break\n    }\n}\n\nflow.set('chartprops',chartprops)\n// message continues to flow but in next node properties will be deleted\n// it will be used only as change event\nreturn msg;\n","outputs":1,"noerr":0,"x":410,"y":210,"wires":[["7cab122e.784c2c"]]},{"id":"b89ca981.40b7f8","type":"inject","z":"cefda5c5.afe5c8","name":"Resend chart format","topic":"","payload":"","payloadType":"date","repeat":"60","crontab":"","once":true,"onceDelay":"1","x":200,"y":340,"wires":[["7cab122e.784c2c"]]},{"id":"3ccde5c7.4c0bfa","type":"comment","z":"cefda5c5.afe5c8","name":"Resend - read comments !","info":"If minimum time is sent to chart, it will be absolute\nChart does not take it as time frame\nSo if change is in minutes, it is reasonable enough\nto send new min value to chart in every minute\nto hold time frame constant","x":210,"y":300,"wires":[]},{"id":"9e271ef8.1ed8","type":"ui_group","z":"","name":"Dashboard","tab":"20d4bd59.a21a22","order":1,"disp":true,"width":"4","collapse":false},{"id":"c4bae040.b8a2","type":"ui_group","z":"","name":"Charts","tab":"20d4bd59.a21a22","order":3,"disp":true,"width":"6"},{"id":"480f6c25.dd7064","type":"ui_group","z":"","name":"Chart Scaling","tab":"20d4bd59.a21a22","order":2,"disp":true,"width":"3","collapse":true},{"id":"20d4bd59.a21a22","type":"ui_tab","z":"","name":"DS Pilot Extrusion Line","icon":"dashboard","order":2}]

In this flow, the input fields can have initial values also set to values stored in flow. But as it is not part of chart manipulation I left it out. Inputs may come in many other ways so...

Nice work, thanks. Do you know why the color changes once I put in a minute value?image Perhaps its changing from dark theme to light theme, I'm not sure.

Yes, if configuration of axis is changed and that configuration does no carry color, it looks for default color and in chart it is actually #666
It may be that dashboard tries to force some color but with some element changed, it does not be reflected.
But it can be changed element by element
For axis it needs to be something like this

EDIT: added colors of grid lines also

msg.ui_control = { 
    options: {
        scales: {
            xAxes: [{
                gridLines:{
                    color:'red',
                    zeroLineColor:'red'
                },
                type: 'time',
                ticks:{
                    fontColor:'white'
                },
                time: {
                    min: d
                }
            }],
            yAxes: [{
                gridLines:{
                    color:'red',
                    zeroLineColor:'red'
                },
                ticks: {
                    fontColor:'white',
                    suggestedMin: flow.get('chartprops').yaxis.min,
                    suggestedMax: flow.get('chartprops').yaxis.max
                }
            }]
        }
    }
}
return msg; 
1 Like

Excellent, I was just going to ask you about the grid lines. It all works, you're very good at this, thanks for your help!

1 Like

Hi,

your solution look great, but I didnt see any changes based ou your flow?:roll_eyes:
I changed the parameters a lot, but i didnt see any changes in the chart
Any idea?

May be if you share the flow?

Sure:

[{"id":"cd9e47f3.c0e718","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"8926ee13.cae4c","type":"ui_gauge","z":"cd9e47f3.c0e718","name":"","group":"eebc9008.c66","order":1,"width":"0","height":"0","gtype":"gage","title":"Melt Temperature","label":"°F","format":"{{value|number:2}}","min":0,"max":"1000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"700","seg2":"900","x":864,"y":456,"wires":[]},{"id":"5d1b683.cca8798","type":"ui_chart","z":"cd9e47f3.c0e718","name":"","group":"195cd998.7632b6","order":1,"width":0,"height":0,"label":"Melt Temperature","chartType":"line","legend":"false","xformat":"auto","interpolate":"linear","nodata":"awaiting data...","dot":false,"ymin":"0","ymax":"1000","removeOlder":"30","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":864,"y":386,"wires":[[]]},{"id":"9f20ea28.10c8a8","type":"ui_text_input","z":"cd9e47f3.c0e718","name":"","label":"Ymax","tooltip":"","group":"b555fa85.b63bd8","order":2,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"ymax","x":384,"y":276,"wires":[["fedec5e0.22c288"]]},{"id":"6a54fbe1.e17f34","type":"ui_text_input","z":"cd9e47f3.c0e718","name":"","label":"Ymin","tooltip":"","group":"b555fa85.b63bd8","order":3,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"ymin","x":384,"y":236,"wires":[["fedec5e0.22c288"]]},{"id":"5b06b35d.8aa9bc","type":"inject","z":"cd9e47f3.c0e718","name":"","topic":"","payload":"","payloadType":"str","repeat":"5","crontab":"","once":false,"onceDelay":"","x":354,"y":426,"wires":[["5dad34da.a11c7c"]]},{"id":"5dad34da.a11c7c","type":"random","z":"cd9e47f3.c0e718","name":"random between 1 - 500","low":"1","high":"500","inte":"false","property":"payload","x":544,"y":426,"wires":[["5d1b683.cca8798","8926ee13.cae4c"]]},{"id":"ae2baf51.522b7","type":"function","z":"cd9e47f3.c0e718","name":"Format chart","func":"\nvar x = flow.get('chartprops').xaxis.min; //time back in milliseconds\nvar d = new Date().getTime() - x //current time in milliseconds - desired time\ndelete msg.payload;\ndelete msg.topic;\n\n//send all modification in one go\nmsg.ui_control = { \n    options: {\n        scales: {\n            xAxes: [{\n                type: 'time',\n                time: {\n                    min: d\n                }\n            }],\n            yAxes: [{\n            ticks: {\n                suggestedMin: flow.get('chartprops').yaxis.min,\n                suggestedMax: flow.get('chartprops').yaxis.max\n            }\n        }]\n        }\n    }\n}\nreturn msg; \n\n","outputs":1,"noerr":0,"x":644,"y":366,"wires":[["5d1b683.cca8798","2223b350.8af84c"]]},{"id":"90b940c6.ee8c","type":"ui_text_input","z":"cd9e47f3.c0e718","name":"","label":"Minutes","tooltip":"","group":"b555fa85.b63bd8","order":3,"width":0,"height":0,"passthru":true,"mode":"number","delay":300,"topic":"time","x":374,"y":196,"wires":[["fedec5e0.22c288"]]},{"id":"fedec5e0.22c288","type":"function","z":"cd9e47f3.c0e718","name":"Store chart options","func":"var chartprops = flow.get('chartprops')\nswitch(msg.topic){\n    case 'time':{\n        chartprops.xaxis.min = msg.payload * 60000\n        break\n    }\n    case 'ymin':{\n        chartprops.yaxis.min = msg.payload\n        break\n    }\n    case 'ymax':{\n        chartprops.yaxis.max = msg.payload\n        break\n    }\n}\n\nflow.set('chartprops',chartprops)\n// message continues to flow but in next node properties will be deleted\n// it will be used only as change event\nreturn msg;\n","outputs":1,"noerr":0,"x":584,"y":236,"wires":[["ae2baf51.522b7"]]},{"id":"b0cf3b4b.89c358","type":"inject","z":"cd9e47f3.c0e718","name":"Resend chart format","topic":"","payload":"","payloadType":"date","repeat":"60","crontab":"","once":true,"onceDelay":"1","x":374,"y":366,"wires":[["ae2baf51.522b7"]]},{"id":"353a44f7.2200ac","type":"comment","z":"cd9e47f3.c0e718","name":"Resend - read comments !","info":"If minimum time is sent to chart, it will be absolute\nChart does not take it as time frame\nSo if change is in minutes, it is reasonable enough\nto send new min value to chart in every minute\nto hold time frame constant","x":384,"y":326,"wires":[]},{"id":"2223b350.8af84c","type":"debug","z":"cd9e47f3.c0e718","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":892,"y":288,"wires":[]},{"id":"eebc9008.c66","type":"ui_group","z":"","name":"Dashboard","tab":"69dacb8c.3da294","order":1,"disp":true,"width":"4","collapse":false},{"id":"195cd998.7632b6","type":"ui_group","z":"","name":"Charts","tab":"69dacb8c.3da294","order":3,"disp":true,"width":"6"},{"id":"b555fa85.b63bd8","type":"ui_group","z":"","name":"Chart Scaling","tab":"69dacb8c.3da294","order":2,"disp":true,"width":"3","collapse":true},{"id":"69dacb8c.3da294","type":"ui_tab","z":"","name":"DS Pilot Extrusion Line","icon":"dashboard","order":2}]

I am now moved away from computers for 2 days. So if anybody ... Otherwise you'll need to wait.

Doesn't look like you put any of the changes to the ui_control @hotNipi suggested. your function is


var x = flow.get('chartprops').xaxis.min; //time back in milliseconds
var d = new Date().getTime() - x //current time in milliseconds - desired time
delete msg.payload;
delete msg.topic;

//send all modification in one go
msg.ui_control = { 
    options: {
        scales: {
            xAxes: [{
                type: 'time',
                time: {
                    min: d
                }
            }],
            yAxes: [{
            ticks: {
                suggestedMin: flow.get('chartprops').yaxis.min,
                suggestedMax: flow.get('chartprops').yaxis.max
            }
        }]
        }
    }
}
return msg; 

If you look in the debug panel you should see an error poping up. There is an error in @hotNipi format-chart function. the variable d does not exist, it should be grabbed from the context variable.

change the line

                time: {
                    min: d
                }

to

                time: {
                    min: flow.get('chartprops').xaxis.min
                }