module.exports = function(RED) { var ui = require('../ui')(RED); var ChartIdList = {}; function ChartNode(config) { RED.nodes.createNode(this, config); this.chartType = config.chartType || "line"; var node = this; var group = RED.nodes.getNode(config.group); if (!group) { return; } var tab = RED.nodes.getNode(group.config.tab); if (!tab) { return; } if (config.width === "0") { delete config.width; } if (config.height === "0") { delete config.height; } // number of pixels wide the chart will be... 43 = sizes.sx - sizes.px //var pixelsWide = ((config.width || group.config.width || 6) - 1) * 43 - 15; if (!tab || !group) { return; } var dnow = Date.now(); var options = { emitOnlyNewValues: true, node: node, tab: tab, group: group, control: { type: 'chart', look: node.chartType, order: config.order, label: config.label, legend: config.legend || false, interpolate: config.interpolate, nodata: config.nodata, width: parseInt(config.width || group.config.width || 6), height: parseInt(config.height || group.config.width/2+1 || 4), ymin: config.ymin, ymax: config.ymax, dot: config.dot || false, xformat : config.xformat || "HH:mm:ss", cutout: parseInt(config.cutout || 0), colors: config.colors, useOneColor: config.useOneColor || false, useUTC: config.useUTC || false, animation: false, spanGaps: false, useDifferentColor: config.useDifferentColor || false, options: {}, className: config.className || '', }, convertBack: function(data) { if (data) { if (data[0] && data[0].hasOwnProperty("values")) { return [data[0].values]; } if (data.length == 0) { return []; } } }, convert: function(value, oldValue, msg) { var converted = {}; if (ChartIdList.hasOwnProperty(node.id) && ChartIdList[node.id] !== node.chartType) { value = []; } if (this.control.look !== node.chartType) { if ((this.control.look === "line") || (node.chartType === "line")) { value = []; } node.chartType = this.control.look; } ChartIdList[node.id] = node.chartType; if (Array.isArray(value)) { if (value.length === 0) { // reset chart converted.update = false; converted.updatedValues = []; return converted; } if (value[0].hasOwnProperty("series") && value[0].hasOwnProperty("data")) { if (!Array.isArray(value[0].series)) { node.error("series not array",msg); return; } if (!Array.isArray(value[0].data)) { node.error("Data not array",msg); return; } var flag = true; for (var dd = 0; dd < value[0].data.length; dd++ ) { if (!isNaN(value[0].data[dd][0])) { flag = false; } } if (node.chartType === "line") { if (flag) { delete value[0].labels; } if (config.removeOlderPoints) { for (var dl=0; dl < value[0].data.length; dl++ ) { if (value[0].data[dl].length > config.removeOlderPoints) { value[0].data[dl] = value[0].data[dl].slice(-config.removeOlderPoints); } } } } else if (node.chartType === "bar" || node.chartType === "horizontalBar") { if (flag) { var tmp = []; for (var d=0; d 0) { refill = true; } } if (l === -1) { oldValue[0].values.labels.push(label); l = oldValue[0].values.labels.length - 1; if (l > 0) { refill = true; } } if (node.chartType === "line") { var time; if (msg.timestamp !== undefined) { time = new Date(msg.timestamp).getTime(); } else { time = new Date().getTime(); } var limitOffsetSec = parseInt(config.removeOlder) * parseInt(config.removeOlderUnit); var limitTime = time - limitOffsetSec * 1000; if (time < limitTime) { return oldValue; } // ignore if too old for window var point = { "x":time, "y":value }; oldValue[0].values.data[s].push(point); converted.newPoint = [{ key:node.id, update:true, values:{ series:series, data:point, labels:label } }]; var rc = 0; for (var u = 0; u < oldValue[0].values.data[s].length; u++) { if (oldValue[0].values.data[s][u].x >= limitTime) { break; } // stop as soon as we are in time window. else { rc += 1; } } if (rc > 0) { oldValue[0].values.data[s].splice(0,rc); } if (config.removeOlderPoints) { var rc2 = oldValue[0].values.data[s].length-config.removeOlderPoints; if (rc2 > 0) { oldValue[0].values.data[s].splice(0,rc2); rc = rc2;} } if (rc > 0) { converted.newPoint[0].remove = rc; } var swap; // insert correctly if a timestamp was earlier. for (var t = oldValue[0].values.data[s].length-2; t>=0; t--) { if (oldValue[0].values.data[s][t].x <= time) { break; // stop if we are in the right place } else { swap = oldValue[0].values.data[s][t]; oldValue[0].values.data[s][t] = oldValue[0].values.data[s][t+1]; oldValue[0].values.data[s][t+1] = swap; } } if (swap) { converted.newPoint = true; } // if inserted then update whole chart if (Date.now() > (dnow + 60000)) { dnow = Date.now(); for (var x = 0; x < oldValue[0].values.data.length; x++) { for (var y = 0; y < oldValue[0].values.data[x].length; y++) { if (oldValue[0].values.data[x][y].x >= limitTime) { break; // stop as soon as we are in time window. } else { oldValue[0].values.data[x].splice(0,1); converted.newPoint = true; y = y - 1; } } } } } else { oldValue[0].values.data[s][l] = value; if (refill) { for (var i = 0; i < oldValue[0].values.series.length; i++) { for (var k = 0; k < oldValue[0].values.labels.length; k++) { oldValue[0].values.data[i][k] = oldValue[0].values.data[i][k] || null; } } } } converted.update = true; converted.updatedValues = oldValue; } return converted; } }; var chgtab = function() { node.receive({payload:"R"}); }; ui.ev.on('changetab', chgtab); var done = ui.add(options); var st = setTimeout(function() { node.emit("input",{payload:"start"}); // trigger a redraw at start to flush out old data. }, 100); node.on("close", function() { if (st) { clearTimeout(st); } ui.ev.removeListener('changetab', chgtab); done(); }) } RED.nodes.registerType("ui_chart", ChartNode); };