Kromek Radangel gamma spectrometer USB HID daemon and WebUI. https://git.unino.de/pvivell/radangel
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.

streamplot.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. function dataSrc(url, config) {
  2. config = config || {};
  3. url = url || [
  4. location.protocol == 'https:' ? 'wss://' : 'ws://',
  5. location.hostname,
  6. location.protocol == 'https:' ? ':' + (location.port || 443) : ':' + (location.port || 80),
  7. '/data/stream.ws'
  8. ].join('');
  9. var ctx = {
  10. 'url': url,
  11. 'ws': {},
  12. 'proto': 'binary',
  13. 'buffer': '',
  14. 'headers': [],
  15. 'data': [],
  16. 'separatorRows': config.separatorRows || "\n",
  17. 'separatorCols': config.separatorCols || "\t",
  18. 'ping': (function() {
  19. var ac = new(window.AudioContext || window.webkitAudioContext)();
  20. var osc = ac.createOscillator();
  21. var gn = ac.createGain();
  22. osc.connect(gn);
  23. gn.connect(ac.destination);
  24. gn.gain.setTargetAtTime(0.0, ac.currentTime, 0.0);
  25. osc.start(ac.currentTime);
  26. return function(freq) {
  27. var d = [0.001, 0.01, 0.1]; // relative decay constants: freq < rise < fall e.g. [0.001, 0.1, 0.2]
  28. var t = 0.100 // 100 ms target time
  29. var v = 0.25 // volume
  30. function energy(channel) {
  31. var c = [405, 4095];
  32. var e = [29.5503, 3628.78];
  33. return e[0] + (e[1] - e[0]) * (channel - c[0]) / (c[1] - c[0]);
  34. };
  35. var f = energy(freq);
  36. osc.frequency.setTargetAtTime(f, ac.currentTime + d[0] * t, d[0] * t);
  37. gn.gain.setTargetAtTime(v, ac.currentTime + d[1] * t, d[1] * t);
  38. gn.gain.setTargetAtTime(0.0, ac.currentTime + d[2] * t, d[2] * t);
  39. }
  40. })()
  41. };
  42. function connect() {
  43. if (ctx.proto == 'binary') {
  44. ctx.ws = new WebSocket(ctx.url, 'binary');
  45. ctx.ws.binaryType = 'arraybuffer';
  46. } else {
  47. ctx.ws = new WebSocket(ctx.url);
  48. }
  49. ctx.ws.onerror = function(evt) {
  50. console.log('WS ERROR: ' + evt.data);
  51. };
  52. ctx.ws.onopen = function(evt) {
  53. console.log('WS OPENED: ' + ctx.url)
  54. };
  55. ctx.ws.onmessage = function(evt) {
  56. //console.log('WS DATA:');
  57. ctx.buffer += ctx.proto == 'binary' ? String.fromCharCode.apply(null, new Uint8Array(evt.data)) : evt.data;
  58. var rows = ctx.buffer.split(ctx.separatorRows);
  59. ctx.buffer = rows.pop(); // buffer incomplete rows
  60. for (var r = 0; r < rows.length; r++) {
  61. var cols = rows[r].split(ctx.separatorCols);
  62. var row = [];
  63. for (var c = 0; c < cols.length; c++) {
  64. row.push((function(str) {
  65. var num = parseFloat(str);
  66. c > 0 ? ctx.ping(num) : true;
  67. return isNaN(num) ? str : num;
  68. })(cols[c]));
  69. }
  70. if (ctx.headers.length == 0) {
  71. ctx.headers = row;
  72. } else {
  73. ctx.data.push(row);
  74. }
  75. }
  76. };
  77. ctx.ws.onclose = function(evt) {
  78. console.log('WS CLOSED:' + evt.code);
  79. window.setTimeout(connect, 1000); // retry every 1000 ms
  80. };
  81. }
  82. connect();
  83. this.getHeaders = function() { // get headers strings array
  84. return ctx.headers;
  85. };
  86. this.getData = function(len) { // get len datasets and delete old data
  87. return ctx.data.splice(0).slice(-1 * (len || 1));
  88. };
  89. return this;
  90. }
  91. function makeData(src, len) {
  92. //console.log('makeData(src,' + len + ')');
  93. len = len || 100;
  94. var rows = src.getData(len);
  95. var cols = src.getHeaders();
  96. var data = [];
  97. var vals = [];
  98. for (var c = 0; c < cols.length; c++) {
  99. vals = [];
  100. for (var r = 0; r < rows.length; r++) {
  101. var val = typeof(rows[r][c]) != 'undefined' ? rows[r][c] : null
  102. //console.log( cols[c] + '[' + r + '] = ' + val);
  103. vals.push(val);
  104. }
  105. data.push(vals);
  106. }
  107. var time = data && data.length > 0 ? data.shift() : null;
  108. return {
  109. 'x': (function(n) {
  110. for (x = []; n--; x.push(time));
  111. return x
  112. })(cols.length - 1),
  113. 'y': data,
  114. 'i': (function(n) {
  115. for (a = []; n--; a[n] = n);
  116. return a;
  117. })(cols.length - 1)
  118. };
  119. }
  120. function formatNames(str, find, replace) {
  121. str = str.toString();
  122. find = find || '_';
  123. replace = replace || ' ';
  124. return str.replace(
  125. new RegExp(
  126. find.replace(
  127. new RegExp(
  128. '([.*+?^=!:${}()|\[\]\/\\])',
  129. 'g'
  130. ),
  131. '\\$1'
  132. ),
  133. 'g'
  134. ),
  135. replace
  136. );
  137. }
  138. function makePlot(ctx, hdr) {
  139. //console.log(JSON.stringify(hdr, null, "\t"));
  140. var data = [];
  141. var cmap = chroma.scale(chroma.bezier(ctx.data.line.color)).mode('lab').correctLightness(false).colors(hdr.length - 1, null);
  142. for (var i = 1; i < hdr.length; i++) {
  143. var trace = JSON.parse(JSON.stringify(ctx.data))
  144. trace['type'] = 'scatter';
  145. trace['mode'] = 'lines';
  146. trace['fill'] = 'tonexty'; // 'tozeroy';
  147. trace['connectgaps'] = true;
  148. trace['name'] = formatNames(hdr[i]);
  149. trace['x'] = new Array(ctx.maxPoints);
  150. trace['y'] = new Array(ctx.maxPoints);
  151. trace['line']['width'] = 1;
  152. trace['line']['color'] = cmap[i - 1].css();
  153. trace['fillcolor'] = cmap[i - 1].alpha(0.2).css()
  154. data.push(trace);
  155. }
  156. document.title = ctx.layout.title;
  157. return Plotly.plot(ctx.id, data, ctx.layout, ctx.config); // https://github.com/plotly/plotly.js/blob/master/src/plot_api/plot_api.js#L53
  158. }
  159. function makeConfig(id, ctx) {
  160. var cmap = chroma.bezier(ctx.colors.graph).scale().colors(5);
  161. var data = {
  162. 'name': 'trace', // replaced by tsv column headers
  163. 'type': 'scatter',
  164. 'mode': 'lines', // 'lines+markers',
  165. 'line': {
  166. 'shape': ctx.shape,
  167. 'smoothing': ctx.smoothing,
  168. 'color': ctx.colors.lines, // interpolated by colormap function
  169. 'width': ctx.size
  170. },
  171. 'marker': {
  172. 'color': cmap[2],
  173. 'size': 1.5 * ctx.size
  174. },
  175. 'y': [0] // replaced by tsv column data
  176. };
  177. var layout = {
  178. 'title': ctx.title.plot,
  179. 'titlefont': {
  180. 'family': 'Courier New, monospace',
  181. 'size': 24,
  182. 'color': cmap[0]
  183. },
  184. 'autosize': true,
  185. // 'width': window.innerWidth,
  186. // 'height': window.innerHeight,
  187. 'plot_bgcolor': cmap[4],
  188. 'paper_bgcolor': cmap[4],
  189. 'xaxis': {
  190. 'title': ctx.title.xaxis,
  191. 'range': [0, 3650],
  192. 'color': cmap[1],
  193. 'titlefont': {
  194. 'family': 'Courier New, monospace',
  195. 'size': 18,
  196. 'color': cmap[0]
  197. },
  198. 'tickwidth': ctx.size,
  199. 'tickcolor': cmap[1],
  200. 'linecolor': cmap[1],
  201. 'gridcolor': cmap[3],
  202. 'zerolinecolor': cmap[1],
  203. 'mirror': true
  204. },
  205. 'yaxis': {
  206. 'title': ctx.title.yaxis,
  207. 'type': 'log',
  208. 'autorange': true,
  209. 'color': cmap[1],
  210. 'titlefont': {
  211. 'family': 'Courier New, monospace',
  212. 'size': 18,
  213. 'color': cmap[0]
  214. },
  215. 'tickwidth': ctx.size,
  216. 'tickcolor': cmap[1],
  217. 'linecolor': cmap[1],
  218. 'gridcolor': cmap[3],
  219. 'zerolinecolor': cmap[1],
  220. 'mirror': true
  221. },
  222. 'margin': {
  223. 'l': 64,
  224. 'r': 32,
  225. 'b': 64,
  226. 't': 80,
  227. 'pad': 0
  228. }
  229. };
  230. var config = {
  231. 'scrollZoom': true,
  232. 'showLink': false,
  233. 'displaylogo': false,
  234. 'modeBarButtonsToAdd': [
  235. {
  236. 'name': 'Toggle Lin/Log',
  237. 'click': function(gd) {
  238. Plotly.relayout(gd, 'yaxis.type', gd.layout.yaxis.type == 'log' ? 'linear' : 'log');
  239. },
  240. 'icon': {
  241. 'width': 1000,
  242. 'ascent': 850,
  243. 'descent': 0,
  244. 'path': 'm 598.75596,109.5 c -8.63281,0 -15.62503,-6.99222 -15.62503,-15.624987 V 62.624975 c 0,-8.632812 6.99222,-15.624987 15.62503,-15.624987 H 770.63093 V -77.999996 H 598.75596 c -8.63281,0 -15.62503,-6.99221 -15.62503,-15.62499 v -31.250024 c 0,-8.63283 6.99222,-15.625 15.62503,-15.625 H 770.63093 V -265.49999 H 598.75596 c -8.63281,0 -15.62503,-6.99222 -15.62503,-15.625 v -31.25002 c 0,-8.63283 6.99222,-15.625 15.62503,-15.625 H 770.63093 V -452.99999 H 598.75596 c -8.63281,0 -15.62503,-6.99222 -15.62503,-15.625 v -31.25002 c 0,-8.63283 6.99222,-15.62499 15.62503,-15.62499 H 770.63093 V -640.49999 C 770.63093,-675.01175 742.64268,-703 708.13096,-703 h -375 c -34.51171,0 -62.50001,27.98825 -62.50001,62.50001 v 874.99998 c 0,34.51171 27.9883,62.50001 62.50001,62.50001 h 375 c 34.51172,0 62.49997,-27.9883 62.49997,-62.50001 V 109.5 Z'
  245. }
  246. }
  247. ],
  248. 'mmodeBarButtonsToRemove': ['sendDataToCloud']
  249. };
  250. return {
  251. 'id': id,
  252. 'data': data,
  253. 'layout': layout,
  254. 'config': config,
  255. 'maxPoints': ctx.maxPoints
  256. };
  257. }
  258. function createGraph(id, ctx) {
  259. ctx = { // sane defaults
  260. 'src': {
  261. 'stream': ctx.src.stream || '/data/stream.ws',
  262. 'init': ctx.src.init || '/data/hist.tsv'
  263. },
  264. 'title': {
  265. 'plot': ctx.title.plot || 'Plot Title',
  266. 'xaxis': ctx.title.xaxis || 'Time / s',
  267. 'yaxis': ctx.title.yaxis || 'Measured Entity / a.u.'
  268. },
  269. 'colors': {
  270. 'graph': ctx.colors.graph || ['black', 'white'],
  271. 'lines': ctx.colors.lines || ['blue', 'cyan', 'lightgreen', 'yellow', 'red']
  272. },
  273. 'maxPoints': ctx.maxPoints || 100,
  274. 'size': ctx.size || 2,
  275. 'scale': ctx.scale || 1.5,
  276. 'shape': ctx.shape || 'linear',
  277. 'smoothing': ctx.smoothing || 1
  278. };
  279. var src = dataSrc([
  280. location.protocol == 'https:' ? 'wss://' : 'ws://',
  281. location.hostname,
  282. location.protocol == 'https:' ? ':' + (localtion.port || 443) : ':' + (location.port || 80),
  283. ctx.src.stream
  284. ].join(''));
  285. var setup = window.setInterval(function() { // wait for headers to arrive
  286. var headers = src.getHeaders();
  287. if (headers.length > 0) {
  288. clearInterval(setup);
  289. makePlot(makeConfig(id, ctx), headers);
  290. function loadInitData(dataUrl) {
  291. var xmlhttp = new XMLHttpRequest();
  292. xmlhttp.open("GET", dataUrl); // xmlhttp.open("GET", url, false);
  293. xmlhttp.onreadystatechange = function() {
  294. if ((xmlhttp.status == 200) && (xmlhttp.readyState == 4)) {
  295. var maxPoints = ctx.maxPoints || 4096;
  296. function energy(channel) {
  297. var c = [405, 4095];
  298. var e = [29.5503, 3628.78];
  299. return e[0] + (e[1] - e[0]) * (channel - c[0]) / (c[1] - c[0]);
  300. };
  301. var data = {
  302. 'x': [(function(n) {
  303. for (a = []; n--; a[n] = energy(n));
  304. return a;
  305. })(maxPoints)],
  306. 'y': [
  307. []
  308. ],
  309. 'i': [],
  310. 't': 0,
  311. 'e': 0
  312. };
  313. var initData = xmlhttp.responseText.split("\n").slice(0, maxPoints).map(parseFloat);
  314. for (i = 0; i < maxPoints; i++) { // prefill initial data
  315. var val = initData[i];
  316. if (isNaN(val)) {
  317. data.y[0][i] = 0;
  318. } else {
  319. data.y[0][i] = val;
  320. data.e += val;
  321. }
  322. }
  323. //console.log(data);
  324. var res = true;
  325. res &= Plotly.relayout(id, {
  326. 'title': ctx.title.plot + ' ' + data.t + 's',
  327. 'yaxis.title': ctx.title.yaxis + ' / ' + data.e
  328. }).catch(function(e) {
  329. //console.log('ERROR[Plotly.relayout()]: ' + e);
  330. });
  331. res &= Plotly.extendTraces(id, {
  332. 'x': data.x,
  333. 'y': data.y
  334. }, [0], maxPoints).catch(function(e) {
  335. //console.log('ERROR[Plotly.extendTraces()]: ' + e);
  336. });
  337. var update = window.setInterval(function() { // update plot in realtime
  338. var rawdata = makeData(src, 1024 * 1024 * 1024); // get at most 1 GB of events
  339. data.i = (function(n) {
  340. for (a = []; n--; a[n] = n);
  341. return a;
  342. })(rawdata.y.length);
  343. for (i = 0; i < rawdata.y.length; i++) {
  344. for (j = 0; j < rawdata.y[i].length; j++) {
  345. data.y[i][rawdata.y[i][j]] += 1;
  346. data.e += 1
  347. }
  348. }
  349. var evtTime = rawdata.x[0][rawdata.x[0].length - 1];
  350. data.t = evtTime ? evtTime : data.t;
  351. return data.i.length > 0 ? (function() {
  352. var res = true;
  353. res &= Plotly.relayout(id, {
  354. 'title': ctx.title.plot + ' ' + data.t + 's',
  355. 'yaxis.title': ctx.title.yaxis + ' / ' + data.e
  356. }).catch(function(e) {
  357. //console.log('ERROR[Plotly.relayout()]: ' + e);
  358. });
  359. res &= Plotly.extendTraces(id, {
  360. 'x': data.x,
  361. 'y': data.y
  362. }, data.i, maxPoints).catch(function(e) {
  363. //console.log('ERROR[Plotly.extendTraces()]: ' + e);
  364. });
  365. return res;
  366. })() : true;
  367. }, 500); // time resolution 500ms
  368. }
  369. };
  370. xmlhttp.send();
  371. }
  372. loadInitData(ctx.src.init);
  373. }
  374. }, 500);
  375. }