smart-energy-monitor/software/flow/node_modules/node-red-dashboard/nodes/ui_chart.js

235 lines
12 KiB
JavaScript

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<value[0].data.length; d++) {
tmp.push([value[0].data[d]]);
}
value[0].data = tmp;
var tmp2 = value[0].series;
value[0].series = value[0].labels;
value[0].labels = tmp2;
}
}
value = [{ key:node.id, values:(value[0] || {series:[], data:[], labels:[]}) }];
}
else {
node.warn("Bad data inject");
value = oldValue;
}
converted.update = false;
converted.updatedValues = value;
}
else {
if (value === false) { value = null; } // let false also create gaps in chart
if (value !== null) { // let null object through for gaps
value = parseFloat(value); // only handle numbers
if (isNaN(value)) { return; } // return if not a number
}
converted.newPoint = true;
var label = msg.label || " ";
var series = msg.series || msg.topic || "";
//if (node.chartType === "bar" || node.chartType === "horizontalBar" || node.chartType === "pie") {
if (node.chartType !== "line") {
if (!msg.series) {
label = msg.topic || msg.label || " ";
series = msg.series || "";
}
}
if ((!oldValue) || (oldValue.length === 0)) {
oldValue = [{ key:node.id, values:{ series:[], data:[], labels:[] } }];
}
//if (node.chartType === "line" || node.chartType === "pie" || node.chartType === "bar" || node.chartType === "horizontalBar" || node.chartType === "radar") { // Line, Bar and Radar
var refill = false;
if (node.chartType === "line") { label = ""; }
var s = oldValue[0].values.series.indexOf(series);
if (!oldValue[0].values.hasOwnProperty("labels")) { oldValue[0].values.labels = []; }
var l = oldValue[0].values.labels.indexOf(label);
if (s === -1) {
oldValue[0].values.series.push(series);
s = oldValue[0].values.series.length - 1;
oldValue[0].values.data[s] = [];
if (l > 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);
};