Simple chart export to html instead of chart node

Hello all,

I am working for a couple of months now on a platform for status reporting using RevolutionPi (raspberry based). I've set up node-red + mdashboard for charts.
As I am new to JS and HTML, most of it came from ideas from this forum, but modified to suit my needs.

Right now, my employer needs charts created in mdashboard exported to PDF (HTML) as a daily, weekly and monthly reports. (Yes, they will be using the dashboard also, but PDF-s are needed for printing and placing them on a panel.

As "HTML-PDF" is not working on my setup and I have failed to fix it (known "failed to load PhantomJS module" error) I would be satisfied in exporting only HTML files for now.

The real problem for me is to output chart in HTML code.

This is only small part of chart generated from an SQLite database:


Please undestand I can only share some small parts of code and charts because it is by all means company property.

Neither of this nodes doesn't generate any type of chart in HTML/PDF:

[{"id":"c401c11a.e4f54","type":"tab","label":"Flow 6","disabled":false,"info":""},{"id":"cdd86f07.51da9","type":"function","z":"c401c11a.e4f54","name":"path","func":"\nvar main_path = \"C:/AAAtemp/\";  //please type your path\nvar name = \"Raport.pdf\";\n\nmsg.filename = main_path + name;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":670,"y":140,"wires":[["1f34f25b.04235e"]]},{"id":"1f34f25b.04235e","type":"HTML-PDF","z":"c401c11a.e4f54","name":"HTML-PDF","output":"file","x":850,"y":140,"wires":[[]]},{"id":"a64bd4e9.a7f5a8","type":"debug","z":"c401c11a.e4f54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":670,"y":220,"wires":[]},{"id":"3aca0508.30343a","type":"inject","z":"c401c11a.e4f54","name":"Go","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":210,"y":100,"wires":[["e410e8f1.3eddb8"]]},{"id":"e410e8f1.3eddb8","type":"function","z":"c401c11a.e4f54","name":"","func":"function getGetOrdinal(n) {\n    var s=[\"th\",\"st\",\"nd\",\"rd\"],\n    v=n%100;\n    return n+(s[(v-20)%10]||s[v]||s[0]);\n }\n\nvar dL = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\nvar dS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nvar mL = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];\nvar mS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];\n\nlet now = new Date();\n\n// Create the chart object\nlet m = {\n    type: 'bar',\n    options: {\n        title: {\n            display:true,\n            text:'Vertical bar with 2 data series'\n        },\n        legend: {\n            display:true\n        },\n        chartArea: {\n            backgroundColor: 'white'\n        },\n        plugins: {\n            datalabels: {\n                display:true,\n                backgroundColor:'whitesmoke',\n                borderRadius:1,\n                padding:1,\n                align: 'right',\n                anchor: function(context) {\n                    //node.send({debug:{dataindex:context.dataIndex}});\n                    if (context.dataIndex == context.dataset.data.length - 1) {\n                        return 'center';\n                    } else {\n                        return 'end';\n                    }\n                },\n                offset:8,\n                formatter:function(value) {\n                    return value > 0 ? value.toLocaleString() : '';\n                }\n            }\n        }\n    },\n    data: {\n        labels:[],\n        datasets: [\n            {\n                label:\"Series 1\",\n                backgroundColor:'rgba(57,97,184,0.8)',\n                data:[]\n            },\n            {\n                label:\"Series 2\",\n                backgroundColor:'rgba(127, 184, 57,0.8)',\n                data:[]\n            }\n        ]\n    }\n}\n\nlet l = Math.floor(Math.random()*50);\nlet k = Math.floor(Math.random()*50);\n\nfor (let i=0; i<10; i++) {\n    l = l + Math.floor(Math.random()*6)-3;\n    m.data.datasets[0].data.push(l);\n    k = k + Math.floor(Math.random()*6)-3;\n    m.data.datasets[1].data.push(k);\n    var d = new Date();\n    d.setTime(now.getTime()-1000*60*60*24*(10-i));\n    let month = \"\" + (d.getMonth() + 1);\n    let day = \"\" + d.getDate();\n    let year = d.getFullYear();\n\n    if (month.length < 2) month = '0' + month;\n    if (day.length < 2) day = '0' + day;\n    m.data.labels.push(day + \".\"+month+\".\"+year);\n}\n\nmsg.payload = m;\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":100,"wires":[["2a0517b6.ceef38"]]},{"id":"78c8ff4c.ac32d","type":"template","z":"c401c11a.e4f54","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<canvas id=\"myChart\" width=\"400\" height=\"400\"></canvas>\n<script>\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            label: '# of Votes',\n            data: [12, 19, 3, 5, 2, 3],\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    },\n    options: {\n        scales: {\n            y: {\n                beginAtZero: true\n            }\n        }\n    }\n});\n</script>","output":"str","x":360,"y":160,"wires":[["cdd86f07.51da9","a2f44b60.d470d8"]]},{"id":"3326bf60.caa66","type":"inject","z":"c401c11a.e4f54","name":"Go","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":210,"y":160,"wires":[["78c8ff4c.ac32d"]]},{"id":"f12b5375.9941e","type":"function","z":"c401c11a.e4f54","name":"","func":"\n/* This function creates a two data sets, with different topics,\n   the same x values and different y values*/\n\n// Change the scale factor each time.\nvar scale = context.get('scale') || 0;\nif (msg.topic == \"Reset\") {\n    scale = 0;\n}\nscale++;\ncontext.set('scale', scale);\n\nmsg.payload = [];       // This will be an array of {topic, data} objects\nvar dataPoints1 = [];   // These will be the first array of data points \nvar dataPoints2 = [];   // These will be the second array of data points \nvar numPoints = 20;     // We will create one more than this.\n\n// For the first data set create a sine wave\nfor (var i=0; i<=numPoints; i++) {\n    var point = {};\n    point.x = i;\n    point.y = Math.sin(2 * 3.14 * (i/numPoints)) * scale/10;\n    // build the data array    \n    dataPoints1.push(point);\n}\n\n// For the second data set create a triangle\nfor (var i=0; i<=numPoints; i++) {\n    var point = {};\n    point.x = i;\n    if (i<(numPoints/2)) {\n        // ramp up\n        point.y = i * scale/10;\n    }\n    else {\n        // ramp down\n        point.y = (numPoints - i) * scale/10;\n    }\n    dataPoints2.push(point);    // add to the array\n}\n\nmsg.action = \"load\";    // This instructs the chart node to paint the data\n\n// The payload is an array of two {topic, data} objects\nmsg.payload = [{topic: \"Sin\", data: dataPoints1},\n                {topic: \"Triangle\", data: dataPoints2}];\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":360,"y":220,"wires":[["203c6f6c.acced"]]},{"id":"64f1f34e.940abc","type":"inject","z":"c401c11a.e4f54","name":"Go","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":210,"y":220,"wires":[["f12b5375.9941e"]]},{"id":"203c6f6c.acced","type":"mui_template","z":"c401c11a.e4f54","group":"2066b3d4.9abffc","name":"","order":0,"width":0,"height":0,"format":"<!-- See the read me comment node. Colin Law's original notes follow -->\n\n<!--\nA node-red Dashboard UI template to draw charts using chart.js\nBefore use download the file Chart.bundle.min.js from chartjs.org and \nsave in an appropriate folder (e.g. .node-red/static). \nIn settings.js set httpStatic to the full path of that folder and restart node-red.\nMake sure that the options for 'Pass through messages' and 'Add output messages' \nin this node are cleared.\nFor basic use set the id and size you want in the canvas tag and set chartID to the id\nSetup chartDef as required for your chart (see the chart.js docs)\nIn addition, for each dataset specify in chartDef the message topic that you will use for that channel.\nTo (optionally) provide the chart with a one-off set of data send the node a message with:\nmsg.action = \"load\"\nmsg.payload = [\n{topic: \"mytopic1\", data: [{x: x1,y:y2},{x:x2,y:y2},...]},\n{topic: \"mytopic2\", data: [{x: x1,y:y2},{x:x2,y:y2},...]},\n...]\nWhere mytopic1 and mytopic2 are the the topics specified in the chartDef\n\nTo provide the chart with data incrementally (for a time series for example)\nsend it messages of the form\n{topic: \"mytopic1\", payload: {x:xvalue,y:yvalue}}\nThe chart will be updated as each sample is provided.\nTo limit the growth of the chart set chartMaxPoints and/or chartTimeSpan in the Chart Helper node\nas described at the head of that node.\nIf you find that chart seems to flicker and scroll bars come and go then try \nsetting a size other than auto in the Size specification for this node.\n\nFor Bar charts the x value is the label for the bar and the y value is the bar value\n\nNote that since the chart samples are stored in the browser then the chart will be cleared each\ntime the browser is refreshed (and will be clear on initially opening the view). In order to \nprovided persistency over browser opening and refresh this node may be used in conjunction with\nthe Chart Helper function node.  Details for its use are in the source of that node.\n\nIf your flow includes more that one instance of this script then the line fetching \nChart.bundle.min.js need only be included in one of them\n-->\n\n<script src=\"/Chart.bundle.min.js\"></script>\n<canvas id=\"myChartSimple1\" width=\"300\" height=\"300\"></canvas>\n<script>\n(function() {\n    var chartID = \"myChartSimple1\";           // set this to the id you have specified in the canvas tag above\n    // setup the chart definition as defined in the chart.js documentation, in addition setting up the topic\n    // for each channel\n    var chartDef = {\n        type: 'line',\n        data: {\n            datasets: [{\n                topic: \"Sin\",    // used here not by chart.js\n                label: \"Sin\",\n                yAxisID: \"1\",\n                fill: false,\n                lineTension: 0,\n                borderColor: \"#ffffff\",\n                pointRadius: 5,\n                pointHoverRadius: 5,\n                pointBorderColor: \"#ffffff\",\n                pointBackgroundColor: \"#ffffff\",\n                backgroundColor:  \"#ffffff\",\n                borderWidth: 1,\n                data: []  // data is written here later\n            }, {\n                topic: \"Triangle\",    // used here not by chart.js\n                label: \"Triangle\",\n                yAxisID: \"2\",\n                fill: false,\n                lineTension: 0,\n                borderColor: \"#ffffff\",\n                pointRadius: 5,\n                pointHoverRadius: 5,\n                pointBorderColor: \"#ffffff\",\n                pointBackgroundColor: \"#ffffff\",\n                backgroundColor:  \"#ffffff\",\n                borderWidth: 1,\n                data: []  // data is written here later\n            }]\n        },\n        options: {\n            scales: {\n                xAxes: [{\n                    type: 'linear',\n                    position: 'bottom'\n                    }\n                ],\n                yAxes: [{\n                    id: \"1\",\n                    ticks: {\n                        min: -1,\n                        max: 1,\n                        stepSize: 0.2\n                    }\n                }, {\n                    id: \"2\",\n                    ticks: {\n                        min: -10,\n                        max: 10,\n                        stepSize: 2\n                    }\n                }]\n            },\n            animation: {\n                duration: 0\n            }\n        }\n    }\n        \n/***** You shouldn't normally need to change anything below here *****/    \n    var myChart = null;\n    var loaded = false;     // indicates whether we have already had a load action\n    var chartTimeSpan;\n    var chartMaxPoints;\n\n    function doChart(msg, scope) {\n        if (!myChart) {\n            // chart does not exist so load the data and create it\n            var ctx = document.getElementById(chartID);\n            myChart = new Chart(ctx, chartDef);     \n        }\n        // chart already exists, update it\n        var datasets = myChart.data.datasets;\n        // is this a load or preload action?\n        if (msg.action === \"load\" || msg.action === \"preload\") {\n            // yes, do not allow preload if we have already had a load\n            // so do it if this is a load or we have not previously had a load\n            if (msg.action === \"load\" || !loaded) {\n                // pick up chartTimeSpan and chartMaxPoints if they have been provided\n                if (typeof msg.chartTimeSpan != 'undefined') {\n                    chartTimeSpan = msg.chartTimeSpan;\n                }\n                if (typeof msg.chartMaxPoints != 'undefined') {\n                    chartMaxPoints = msg.chartMaxPoints;\n                }\n                    \n                // replace existing data for matching topics\n                for (var j = 0; j < msg.payload.length; j++) {\n                    var topic = msg.payload[j].topic;\n                    // find it in the chart\n                    for (var i = 0; i < datasets.length; i++) {\n                        if (datasets[i].topic == topic) {\n                            // if stripping old samples by time is required then ensure the x value is Date\n                            if (chartTimeSpan > 0 ) {\n                                var data = msg.payload[j].data;\n                                for (var k = 0; k < data.length; k++) {\n                                    if (typeof data[k].x === \"string\") {\n                                        data[k].x = new Date(data[k].x);\n                                    }\n                                }\n                            }\n                            if (chartDef.type !== \"bar\") {\n                                datasets[i].data = msg.payload[j].data;\n                            } else {\n                                // bar chart so x values must go to labels and y values to dataset\n                                datasets[i].data = [];\n                                myChart.data.labels = [];\n                                var data = msg.payload[j].data;\n                                for (var k = 0; k < data.length; k++) {\n                                    datasets[i].data.push(data[k].y);\n                                    myChart.data.labels.push(data[k].x);\n                                }\n                            }\n                            break;\n                        }\n                    }\n                }\n            }\n            if (msg.action === \"load\") loaded = true;\n            myChart.update();\n        } else {\n            // does the topic match one of the datasets?\n            for (var i = 0; i < datasets.length; i++) {\n                if (datasets[i].topic == msg.topic) {\n                    // if stripping old samples by time is required then ensure the x value is Date\n                    if (chartTimeSpan > 0 && typeof msg.payload.x === \"string\") {\n                        msg.payload.x = new Date(msg.payload.x);\n                    }\n                    if (chartDef.type !== \"bar\") {\n                        datasets[i].data.push(msg.payload);\n                    } else {\n                         // bar chart so x value must go to labels and y value to dataset\n                        datasets[i].data.push(msg.payload.y);\n                        myChart.data.labels.push(msg.payload.x);\n                    }\n                    myChart.update();\n                    break;\n                }\n            }\n        }\n        // strip off samples older than now\n        // charTimeSpan == 0 implies don't do it\n        var shifted = false;\n        if (chartTimeSpan > 0) {\n            var now = new Date();\n            var oldestTimeAllowed = now - chartTimeSpan;\n            for (var i = 0; i < datasets.length; i++) {\n                dataset = datasets[i];\n                while(dataset.data[0] && getTime(dataset.data[0].x) < oldestTimeAllowed) {\n                    dataset.data.shift();\n                    shifted = true;\n                }\n            }\n        }\n        // strip samples off the front if there are now too many\n        // charTimeSpan == 0 implies don't do it\n        if (chartMaxPoints > 0) {\n            for (var i = 0; i < datasets.length; i++) {\n                dataset = datasets[i];\n                while(dataset.data.length > chartMaxPoints) {\n                    dataset.data.shift();\n                    shifted = true;\n                }\n            }\n        }\n        if (shifted) {\n            myChart.update();\n        }\n    };\n\n    // gets the time of an x value, works for strings or Date types\n    function getTime(x) {\n        if (typeof x === \"string\") x = new Date(x);\n        return x.getTime();\n    }\n    \n    // builds the preload message for sending back to the chart helper\n    function preloadMsg() {\n        var preMsg = {action: \"preload\", payload: \"preload\"};\n        // build array of topics in chart\n        var topics = [];\n        for (var i=0; i<chartDef.data.datasets.length; i++) {\n            topics.push(chartDef.data.datasets[i].topic);\n        }\n        preMsg.topics = topics;\n        // has the chart already been created\n        if (myChart) {\n            preMsg.lastXValue = 1;\n        } else {\n            preMsg.lastXValue = 0;\n        }\n        return preMsg;\n    }\n\n    (function(scope) {\n        // this code gets run when the a view is opened on the node in the browser\n        // send a preload message back to node red to ask it send\n        // us a complete set of data. Pass down max points and time span to the helper node for it to use\n        // plus an array of the topics of interest\n        scope.send( preloadMsg() );\n        \n        scope.$watch('msg', function(msg) {\n            if (msg) {\n                doChart(msg, scope);\n            }\n        });\n    })(scope);\n})();\n</script>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":520,"y":220,"wires":[["cdd86f07.51da9","a64bd4e9.a7f5a8"]]},{"id":"77c05a66.a407f4","type":"mui_template","z":"c401c11a.e4f54","group":"2066b3d4.9abffc","name":"","order":1,"width":0,"height":0,"format":"<script src=\"/Chart.bundle.min.js\"></script>\n<canvas id=\"myChartSimple1\" width=\"300\" height=\"300\"></canvas>\n<script>\n    function getGetOrdinal(n) {\n        var s=[\"th\",\"st\",\"nd\",\"rd\"],\n        v=n%100;\n        return n+(s[(v-20)%10]||s[v]||s[0]);\n     }\n    \n    var dL = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n    var dS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n    var mL = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];\n    var mS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];\n    \n    let now = new Date();\n    \n    // Create the chart object\n    let m = {\n        type: 'bar',\n        options: {\n            title: {\n                display:true,\n                text:'Vertical bar with 2 data series'\n            },\n            legend: {\n                display:true\n            },\n            chartArea: {\n                backgroundColor: 'white'\n            },\n            plugins: {\n                datalabels: {\n                    display:true,\n                    backgroundColor:'whitesmoke',\n                    borderRadius:1,\n                    padding:1,\n                    align: 'right',\n                    anchor: function(context) {\n                        //node.send({debug:{dataindex:context.dataIndex}});\n                        if (context.dataIndex == context.dataset.data.length - 1) {\n                            return 'center';\n                        } else {\n                            return 'end';\n                        }\n                    },\n                    offset:8,\n                    formatter:function(value) {\n                        return value > 0 ? value.toLocaleString() : '';\n                    }\n                }\n            }\n        },\n        data: {\n            labels:[],\n            datasets: [\n                {\n                    label:\"Series 1\",\n                    backgroundColor:'rgba(57,97,184,0.8)',\n                    data:[]\n                },\n                {\n                    label:\"Series 2\",\n                    backgroundColor:'rgba(127, 184, 57,0.8)',\n                    data:[]\n                }\n            ]\n        }\n    }\n    \n    let l = Math.floor(Math.random()*50);\n    let k = Math.floor(Math.random()*50);\n    \n    for (let i=0; i<10; i++) {\n        l = l + Math.floor(Math.random()*6)-3;\n        m.data.datasets[0].data.push(l);\n        k = k + Math.floor(Math.random()*6)-3;\n        m.data.datasets[1].data.push(k);\n        var d = new Date();\n        d.setTime(now.getTime()-1000*60*60*24*(10-i));\n        let month = \"\" + (d.getMonth() + 1);\n        let day = \"\" + d.getDate();\n        let year = d.getFullYear();\n    \n        if (month.length < 2) month = '0' + month;\n        if (day.length < 2) day = '0' + day;\n        m.data.labels.push(day + \".\"+month+\".\"+year);\n    }\n    \n    msg.payload = m;\n    \n    return msg;\n\n</script>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":360,"y":280,"wires":[["cdd86f07.51da9","86abac39.a3eed"]]},{"id":"87394fa2.03694","type":"inject","z":"c401c11a.e4f54","name":"Go","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":210,"y":280,"wires":[["77c05a66.a407f4"]]},{"id":"2a0517b6.ceef38","type":"mui_template","z":"c401c11a.e4f54","group":"2066b3d4.9abffc","name":"","order":2,"width":0,"height":0,"format":"<div ng-bind-html=\"msg.payload\"></div>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":480,"y":100,"wires":[["cdd86f07.51da9","38cae72c.b50868"]]},{"id":"38cae72c.b50868","type":"debug","z":"c401c11a.e4f54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":670,"y":100,"wires":[]},{"id":"a2f44b60.d470d8","type":"debug","z":"c401c11a.e4f54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":670,"y":180,"wires":[]},{"id":"86abac39.a3eed","type":"debug","z":"c401c11a.e4f54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":670,"y":280,"wires":[]},{"id":"2066b3d4.9abffc","type":"mui_group","name":"Default","tab":"d17681e4.13e31","disp":true,"width":"6","collapse":false},{"id":"d17681e4.13e31","type":"mui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

For testing purposes I am using HTML-PDF which runs localy with no problems (I really don't why but creating a valih HTML is my main focus)

I have seached entire forum but failed to find a simple chart exported as a html. (checked using debug node)

Thank you all in advance.

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.