Save dashboard chart as an image

1,080 views
Skip to first unread message

Csongor Varga

unread,
Apr 5, 2017, 2:17:45 PM4/5/17
to Node-RED
Hi All,

This is probably not an easy question. I understand that the no such outputs to do that, but is it possible to save the image of a chart? Like for example, can I generate a chart and send the image of it in an email?

Thanks,
Csongor

steve rickus

unread,
Apr 5, 2017, 3:38:27 PM4/5/17
to Node-RED
Yes, I just did something similar last month, as luck would have it. I was able to use the free plotly.js library to render the chart in memory, then when finished grab a base64 encoded stream of the jpg or png image (i think).

Here is the bit of the flow I used to test it with a few static data points (of course it only works when the /red/ui page is active in the browser):

[
    {
        "id": "6a22305c.5135a",
        "type": "inject",
        "z": "d388adbc.37c1c",
        "name": "Influx Query results",
        "topic": "Time-Series sample",
        "payload": "[ \t[ \t\t{ \t\t  \"time\": \"2016-12-01T19:59:04.619Z\", \t\t  \"value\": \"22\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:09:05.578Z\", \t\t  \"value\": \"20.5\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:17:24.808Z\", \t\t  \"value\": \"20\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:37:24.81Z\", \t\t  \"value\": \"19.5\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:47:24.812Z\", \t\t  \"value\": \"19\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:57:24.813Z\", \t\t  \"value\": \"20\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T21:17:24.816Z\", \t\t  \"value\": \"21\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T21:37:24.819Z\", \t\t  \"value\": \"21.5\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T21:47:24.821Z\", \t\t  \"value\": \"20\" \t\t} \t], \t[ \t\t{ \t\t  \"time\": \"2016-12-01T19:59:04.619Z\", \t\t  \"value\": \"12\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:09:05.578Z\", \t\t  \"value\": \"12.5\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:17:24.808Z\", \t\t  \"value\": \"12\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:37:24.81Z\", \t\t  \"value\": \"13.5\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:47:24.812Z\", \t\t  \"value\": \"15\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T20:57:24.813Z\", \t\t  \"value\": \"16.5\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T21:17:24.816Z\", \t\t  \"value\": \"14\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T21:37:24.819Z\", \t\t  \"value\": \"12.5\" \t\t}, \t\t{ \t\t  \"time\": \"2016-12-01T21:47:24.821Z\", \t\t  \"value\": \"11\" \t\t} \t] ]",
        "payloadType": "json",
        "repeat": "",
        "crontab": "",
        "once": false,
        "x": 150,
        "y": 420,
        "wires": [
            [
                "3d4fcd5e.de1d22",
                "6daa4bcb.026414"
            ]
        ]
    },
    {
        "id": "6daa4bcb.026414",
        "type": "change",
        "z": "d388adbc.37c1c",
        "name": "Plotly data formatter",
        "rules": [
            {
                "t": "set",
                "p": "data",
                "pt": "msg",
                "to": "msg.payload.\t{\t    \"x\": [time],\t    \"y\": [value],\t    \"mode\": \"lines+markers\"\t}",
                "tot": "jsonata"
            },
            {
                "t": "set",
                "p": "layout",
                "pt": "msg",
                "to": "{\t    \"title\": msg.topic\t}",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 320,
        "y": 500,
        "wires": [
            [
                "91149cf2.19ee9",
                "f72c73d1.c59c2"
            ]
        ]
    },
    {
        "id": "91149cf2.19ee9",
        "type": "ui_template",
        "z": "d388adbc.37c1c",
        "group": "6179fa60.1ff7e4",
        "name": "Plotly chart",
        "order": 1,
        "width": "0",
        "height": "0",
        "format": "<script src=\"https://cdn.plot.ly/plotly-latest.min.js\"></script>\n\n<div id=\"myPlot\" style=\"width: 400px; height: 300px;\">\n    <!-- Plotly chart will be drawn inside this DIV -->\n</div>\n\n<script>\n    var points = [];\n    var layout = { mode: \"line\" };\n    //var plotly = Plotly.newPlot(\"myPlot\", points, layout);\n\n// Lambda function to access the Angular Scope \n;(function(scope) { \n    //Have to use $watch - as we can't directly access $scope - so we pick up new, incoming msg's \n    scope.$watch('msg.data', function(newVal, oldVal) { \n        console.log('- Scope.msg -') \n        console.dir(scope.msg)\n\n        if (scope.msg && scope.msg.data) {\n            var img;\n            createPlot(scope.msg.data, scope.msg.layout)\n            .then(function(gd) {\n                img = Plotly.toImage(gd, {\n                    format: 'png',\n                    height: 400,\n                    width: 300\n                })\n                .then(function(raw) {\n                    scope.msg.payload = raw;\n                    scope.send(scope.msg);\n                })\n            })\n        }\n    })\n})(scope);\n\nfunction createPlot(data, opts) {\n    points = data;\n    layout = opts;\n    return Plotly.newPlot(\"myPlot\", data, opts);\n};\n\n</script>",
        "storeOutMessages": false,
        "fwdInMessages": false,
        "x": 630,
        "y": 500,
        "wires": [
            [
                "6acb86b8.659a38"
            ]
        ]
    },
    {
        "id": "6acb86b8.659a38",
        "type": "debug",
        "z": "d388adbc.37c1c",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 830,
        "y": 500,
        "wires": []
    },
    {
        "id": "f72c73d1.c59c2",
        "type": "debug",
        "z": "d388adbc.37c1c",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "true",
        "x": 530,
        "y": 560,
        "wires": []
    },
    {
        "id": "6179fa60.1ff7e4",
        "type": "ui_group",
        "z": "",
        "name": "Plotly Data",
        "tab": "43ebf368.6e761c",
        "order": 4,
        "disp": true,
        "width": "8"
    },
    {
        "id": "43ebf368.6e761c",
        "type": "ui_tab",
        "z": "",
        "name": "Query Data",
        "icon": "dashboard",
        "order": 1
    }
]

The last debug node shows the raw data string, which may not be the best format for emailing to someone. But you should be able to create a temp file with the image in it, and send it through email as an attachment. Cheers!
--
Steve

Csongor Varga

unread,
Apr 5, 2017, 6:00:01 PM4/5/17
to Node-RED
Wow, this is absolutely awesome, can't wait to integrate into my code and try it out. And I can hide the template somewhere on a tab which is not visible so it does not show up on the screen.

Csongor Varga

unread,
Apr 7, 2017, 4:48:48 AM4/7/17
to Node-RED
Hi Steve,

I managed to do almost everything I wanted. I formatted the SQL results and feed it into plotly which works with one or more series. I could also save the png and sent it as an email. But as you said this only works if I have the template is on the screen. I am using dashboard chart for the main display, so ploty showing up as a second chart is a bit confusing. Do you have any ideas how can I hide it after the plot is generated?

Regards,
Csongor


On Wednesday, April 5, 2017 at 9:38:27 PM UTC+2, steve rickus wrote:

steve rickus

unread,
Apr 7, 2017, 9:00:16 AM4/7/17
to Node-RED
Although I have not tried it, I would think you can just hide it with a bit of CSS -- it should not need to be visible in order to be active. Although it may mess with the spacing or layout of the other dashboard components since the dashboard container will still be visible...

Inside the ui_template node, where the div is defined for the plot container, add the style something like this:

<div id="myPlot" style="width: 400px; height: 300px; display: none;">
    <!-- Plotly chart will be drawn inside this DIV -->
</div>

A better solution would be to put the ui_template into a non-displayed section of the dashboard -- something that the guys have agreed is a good option to have, but has not been implemented yet, afaik.

Csongor Varga

unread,
Apr 7, 2017, 11:09:58 AM4/7/17
to Node-RED
Thanks, I will try the CCS solution. I tried hiding it in the on a tab which is not used, but in that case it is not getting rendered. The message goes into the template node, but since it is not getting rendered on the browser the script is not getting executed. When I navigate to that tab is springs to life and sends the email, but not until I open that tab....
Reply all
Reply to author
Forward
0 new messages