node-red-dashboard
Version:
A set of dashboard nodes for Node-RED
223 lines (209 loc) • 10.5 kB
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 options = {
emitOnlyNewValues: false,
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,
xformat : config.xformat || "HH:mm:SS",
cutout: parseInt(config.cutout || 0),
colors: config.colors
},
convertBack: function(data) {
if (data[0]) {
if (data[0] && data[0].hasOwnProperty("values") && data[0].values.hasOwnProperty("series") ) {
var o = [];
for (var i=0; i<data[0].values.series.length; i++) {
if (data[0].values.data[i] !== undefined) {
if (typeof data[0].values.data[i] === "number") {
o.push({ key:data[0].values.series[i], values:data[0].values.data[i] });
}
else {
var d = data[0].values.data[i].map(function(i) { return [i.x, i.y]; });
o.push({ key:data[0].values.series[i], values:d });
}
}
}
data = o;
}
return data;
}
},
convert: function(value, oldValue, msg) {
if (ChartIdList.hasOwnProperty(node.id) && ChartIdList[node.id] !== node.chartType) {
value = "changed";
oldValue = [];
}
ChartIdList[node.id] = node.chartType;
var converted = {};
if (Array.isArray(value)) {
if (node.chartType !== "line") {
var nb = { series:[], data:[], labels:[] };
for (var v in value) {
if (value.hasOwnProperty(v)) {
nb.data.push(value[v].values);
nb.series.push(value[v].key);
}
}
value = [{key:node.id, values:nb}];
}
else {
if (value[0] && value[0].hasOwnProperty("values")) {
if (Array.isArray(value[0].values)) { // Handle "old" style data array
var na = {series:[], data:[]};
for (var n=0; n<value.length; n++) {
na.series.push(value[n].key);
na.data.push(value[n].values.map(function(i) {
return {x:i[0], y:i[1]};
}));
}
value = [{ key:node.id, values:na}];
}
}
}
converted.update = false;
converted.updatedValues = value;
}
else {
value = parseFloat(value);
if (isNaN(value)) { return oldValue || []; }
var topic = msg.topic || 'Series 1';
var storageKey = node.id;
var found;
if (!oldValue) { oldValue = [];}
if (node.chartType !== "line") { // handle bar and pie type data
if (oldValue.length === 0) {
oldValue = [{ key: storageKey, values: { data:[], series:[], labels:[] }
}]
}
for (var i=0; i<oldValue[0].values.series.length; i++) {
if (oldValue[0].values.series[i] === topic) {
oldValue[0].values.data[i] = value;
found = true;
break;
}
}
if (!found) {
oldValue[0].values.series.push(topic);
oldValue[0].values.labels.push(topic);
oldValue[0].values.data.push(value);
}
converted.update = false;
converted.updatedValues = oldValue;
}
else { // Line chart
// Find the chart data
for (var j = 0; j < oldValue.length; j++) {
if (oldValue[j].key === storageKey) {
found = oldValue[j];
break;
}
}
// Setup the data structure if this is the first time
if (!found) {
found = {
key: storageKey,
values: {
series: [],
data: []
}
}
oldValue.push(found);
}
// Create the new point and add to the dataset
// Create series if it doesn't exist
var seriesIndex = found.values.series.indexOf(topic);
if (seriesIndex === -1) {
found.values.series.push(topic);
found.values.data.push([]);
seriesIndex = found.values.series.indexOf(topic);
}
// Add a new point
var time = new Date().getTime();
// Add the data to the correct series
var point = {"x": time, "y": value};
found.values.data[seriesIndex].push(point);
// Remove datapoints older than a certain time
var limitOffsetSec = parseInt(config.removeOlder) * parseInt(config.removeOlderUnit);
var limitTime = new Date().getTime() - limitOffsetSec * 1000;
var pointsLimit = config.removeOlderPoints;
var removed = [];
var removeSeries = [];
oldValue[0].values.data.forEach(function(series, seriesIndex) {
var i = 0;
while (i < series.length && series[i]['x'] < limitTime) { i++; }
if (i > 0) {
series.splice(0, i);
removed.push({seriesIndex: seriesIndex, noPoints: i});
}
// Remove old datapoints if total is greater than points limit
if (pointsLimit > 0 && series.length > pointsLimit) {
var noToRemove = series.length - pointsLimit;
series.splice(0, noToRemove);
removed.push({seriesIndex: seriesIndex, noPoints: noToRemove});
}
if (series.length === 0) { removeSeries.push(seriesIndex); }
});
// Ensure series match up
removeSeries.forEach(function(index) {
oldValue[0].values.series.splice(index, 1);
oldValue[0].values.data.splice(index, 1);
});
// If more datapoints than number of pixels wide...
// if (found.values.data[seriesIndex].length % pixelsWide === 0) {
// node.warn("More than "+found.values.length+" datapoints");
// }
// Return an object including the new point and all the values
converted.update = true;
converted.newPoint = [{
key: topic,
update: true,
removedData: removed,
removedSeries: removeSeries,
values: {
data: point
}
}];
converted.updatedValues = oldValue;
}
}
return converted;
}
};
var done = ui.add(options);
setTimeout(function() {
node.emit("input",{payload:"start"}); // trigger a redraw at start to flush out old data.
if (node.wires.length === 2) { // if it's an old version of the node honour it
node.send([null, {payload:"restore", for:node.id}]);
}
}, 100);
node.on("close", done);
}
RED.nodes.registerType("ui_chart", ChartNode);
};