Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ui_chart.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. module.exports = function(RED) {
  2. var ui = require('../ui')(RED);
  3. var ChartIdList = {};
  4. function ChartNode(config) {
  5. RED.nodes.createNode(this, config);
  6. this.chartType = config.chartType || "line";
  7. var node = this;
  8. var group = RED.nodes.getNode(config.group);
  9. if (!group) { return; }
  10. var tab = RED.nodes.getNode(group.config.tab);
  11. if (!tab) { return; }
  12. if (config.width === "0") { delete config.width; }
  13. if (config.height === "0") { delete config.height; }
  14. // number of pixels wide the chart will be... 43 = sizes.sx - sizes.px
  15. //var pixelsWide = ((config.width || group.config.width || 6) - 1) * 43 - 15;
  16. if (!tab || !group) { return; }
  17. var dnow = Date.now();
  18. var options = {
  19. emitOnlyNewValues: true,
  20. node: node,
  21. tab: tab,
  22. group: group,
  23. control: {
  24. type: 'chart',
  25. look: node.chartType,
  26. order: config.order,
  27. label: config.label,
  28. legend: config.legend || false,
  29. interpolate: config.interpolate,
  30. nodata: config.nodata,
  31. width: parseInt(config.width || group.config.width || 6),
  32. height: parseInt(config.height || group.config.width/2+1 || 4),
  33. ymin: config.ymin,
  34. ymax: config.ymax,
  35. dot: config.dot || false,
  36. xformat : config.xformat || "HH:mm:ss",
  37. cutout: parseInt(config.cutout || 0),
  38. colors: config.colors,
  39. useOneColor: config.useOneColor || false,
  40. useUTC: config.useUTC || false,
  41. animation: false,
  42. spanGaps: false,
  43. useDifferentColor: config.useDifferentColor || false,
  44. options: {},
  45. className: config.className || '',
  46. },
  47. convertBack: function(data) {
  48. if (data) {
  49. if (data[0] && data[0].hasOwnProperty("values")) {
  50. return [data[0].values];
  51. }
  52. if (data.length == 0) {
  53. return [];
  54. }
  55. }
  56. },
  57. convert: function(value, oldValue, msg) {
  58. var converted = {};
  59. if (ChartIdList.hasOwnProperty(node.id) && ChartIdList[node.id] !== node.chartType) {
  60. value = [];
  61. }
  62. if (this.control.look !== node.chartType) {
  63. if ((this.control.look === "line") || (node.chartType === "line")) { value = []; }
  64. node.chartType = this.control.look;
  65. }
  66. ChartIdList[node.id] = node.chartType;
  67. if (Array.isArray(value)) {
  68. if (value.length === 0) { // reset chart
  69. converted.update = false;
  70. converted.updatedValues = [];
  71. return converted;
  72. }
  73. if (value[0].hasOwnProperty("series") && value[0].hasOwnProperty("data")) {
  74. if (!Array.isArray(value[0].series)) { node.error("series not array",msg); return; }
  75. if (!Array.isArray(value[0].data)) { node.error("Data not array",msg); return; }
  76. var flag = true;
  77. for (var dd = 0; dd < value[0].data.length; dd++ ) {
  78. if (!isNaN(value[0].data[dd][0])) { flag = false; }
  79. }
  80. if (node.chartType === "line") {
  81. if (flag) { delete value[0].labels; }
  82. if (config.removeOlderPoints) {
  83. for (var dl=0; dl < value[0].data.length; dl++ ) {
  84. if (value[0].data[dl].length > config.removeOlderPoints) {
  85. value[0].data[dl] = value[0].data[dl].slice(-config.removeOlderPoints);
  86. }
  87. }
  88. }
  89. }
  90. else if (node.chartType === "bar" || node.chartType === "horizontalBar") {
  91. if (flag) {
  92. var tmp = [];
  93. for (var d=0; d<value[0].data.length; d++) {
  94. tmp.push([value[0].data[d]]);
  95. }
  96. value[0].data = tmp;
  97. var tmp2 = value[0].series;
  98. value[0].series = value[0].labels;
  99. value[0].labels = tmp2;
  100. }
  101. }
  102. value = [{ key:node.id, values:(value[0] || {series:[], data:[], labels:[]}) }];
  103. }
  104. else {
  105. node.warn("Bad data inject");
  106. value = oldValue;
  107. }
  108. converted.update = false;
  109. converted.updatedValues = value;
  110. }
  111. else {
  112. if (value === false) { value = null; } // let false also create gaps in chart
  113. if (value !== null) { // let null object through for gaps
  114. value = parseFloat(value); // only handle numbers
  115. if (isNaN(value)) { return; } // return if not a number
  116. }
  117. converted.newPoint = true;
  118. var label = msg.label || " ";
  119. var series = msg.series || msg.topic || "";
  120. //if (node.chartType === "bar" || node.chartType === "horizontalBar" || node.chartType === "pie") {
  121. if (node.chartType !== "line") {
  122. if (!msg.series) {
  123. label = msg.topic || msg.label || " ";
  124. series = msg.series || "";
  125. }
  126. }
  127. if ((!oldValue) || (oldValue.length === 0)) {
  128. oldValue = [{ key:node.id, values:{ series:[], data:[], labels:[] } }];
  129. }
  130. //if (node.chartType === "line" || node.chartType === "pie" || node.chartType === "bar" || node.chartType === "horizontalBar" || node.chartType === "radar") { // Line, Bar and Radar
  131. var refill = false;
  132. if (node.chartType === "line") { label = ""; }
  133. var s = oldValue[0].values.series.indexOf(series);
  134. if (!oldValue[0].values.hasOwnProperty("labels")) { oldValue[0].values.labels = []; }
  135. var l = oldValue[0].values.labels.indexOf(label);
  136. if (s === -1) {
  137. oldValue[0].values.series.push(series);
  138. s = oldValue[0].values.series.length - 1;
  139. oldValue[0].values.data[s] = [];
  140. if (l > 0) { refill = true; }
  141. }
  142. if (l === -1) {
  143. oldValue[0].values.labels.push(label);
  144. l = oldValue[0].values.labels.length - 1;
  145. if (l > 0) { refill = true; }
  146. }
  147. if (node.chartType === "line") {
  148. var time;
  149. if (msg.timestamp !== undefined) { time = new Date(msg.timestamp).getTime(); }
  150. else { time = new Date().getTime(); }
  151. var limitOffsetSec = parseInt(config.removeOlder) * parseInt(config.removeOlderUnit);
  152. var limitTime = time - limitOffsetSec * 1000;
  153. if (time < limitTime) { return oldValue; } // ignore if too old for window
  154. var point = { "x":time, "y":value };
  155. oldValue[0].values.data[s].push(point);
  156. converted.newPoint = [{ key:node.id, update:true, values:{ series:series, data:point, labels:label } }];
  157. var rc = 0;
  158. for (var u = 0; u < oldValue[0].values.data[s].length; u++) {
  159. if (oldValue[0].values.data[s][u].x >= limitTime) { break; } // stop as soon as we are in time window.
  160. else { rc += 1; }
  161. }
  162. if (rc > 0) { oldValue[0].values.data[s].splice(0,rc); }
  163. if (config.removeOlderPoints) {
  164. var rc2 = oldValue[0].values.data[s].length-config.removeOlderPoints;
  165. if (rc2 > 0) { oldValue[0].values.data[s].splice(0,rc2); rc = rc2;}
  166. }
  167. if (rc > 0) { converted.newPoint[0].remove = rc; }
  168. var swap; // insert correctly if a timestamp was earlier.
  169. for (var t = oldValue[0].values.data[s].length-2; t>=0; t--) {
  170. if (oldValue[0].values.data[s][t].x <= time) {
  171. break; // stop if we are in the right place
  172. }
  173. else {
  174. swap = oldValue[0].values.data[s][t];
  175. oldValue[0].values.data[s][t] = oldValue[0].values.data[s][t+1];
  176. oldValue[0].values.data[s][t+1] = swap;
  177. }
  178. }
  179. if (swap) { converted.newPoint = true; } // if inserted then update whole chart
  180. if (Date.now() > (dnow + 60000)) {
  181. dnow = Date.now();
  182. for (var x = 0; x < oldValue[0].values.data.length; x++) {
  183. for (var y = 0; y < oldValue[0].values.data[x].length; y++) {
  184. if (oldValue[0].values.data[x][y].x >= limitTime) {
  185. break; // stop as soon as we are in time window.
  186. }
  187. else {
  188. oldValue[0].values.data[x].splice(0,1);
  189. converted.newPoint = true;
  190. y = y - 1;
  191. }
  192. }
  193. }
  194. }
  195. }
  196. else {
  197. oldValue[0].values.data[s][l] = value;
  198. if (refill) {
  199. for (var i = 0; i < oldValue[0].values.series.length; i++) {
  200. for (var k = 0; k < oldValue[0].values.labels.length; k++) {
  201. oldValue[0].values.data[i][k] = oldValue[0].values.data[i][k] || null;
  202. }
  203. }
  204. }
  205. }
  206. converted.update = true;
  207. converted.updatedValues = oldValue;
  208. }
  209. return converted;
  210. }
  211. };
  212. var chgtab = function() {
  213. node.receive({payload:"R"});
  214. };
  215. ui.ev.on('changetab', chgtab);
  216. var done = ui.add(options);
  217. var st = setTimeout(function() {
  218. node.emit("input",{payload:"start"}); // trigger a redraw at start to flush out old data.
  219. }, 100);
  220. node.on("close", function() {
  221. if (st) { clearTimeout(st); }
  222. ui.ev.removeListener('changetab', chgtab);
  223. done();
  224. })
  225. }
  226. RED.nodes.registerType("ui_chart", ChartNode);
  227. };