| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- function dataSrc(url, config) {
- config = config || {};
- url = url || [
- location.protocol == 'https:' ? 'wss://' : 'ws://',
- location.hostname,
- location.protocol == 'https:' ? ':' + (location.port || 443) : ':' + (location.port || 80),
- '/data/stream.ws'
- ].join('');
- var ctx = {
- 'url': url,
- 'ws': {},
- 'proto': 'binary',
- 'buffer': '',
- 'headers': [],
- 'data': [],
- 'separatorRows': config.separatorRows || "\n",
- 'separatorCols': config.separatorCols || "\t",
- 'ping': (function() {
- var ac = new(window.AudioContext || window.webkitAudioContext)();
- var osc = ac.createOscillator();
- var gn = ac.createGain();
- osc.connect(gn);
- gn.connect(ac.destination);
- gn.gain.setTargetAtTime(0.0, ac.currentTime, 0.0);
- osc.start(ac.currentTime);
- return function(freq) {
- var d = [0.001, 0.01, 0.1]; // relative decay constants: freq < rise < fall e.g. [0.001, 0.1, 0.2]
- var t = 0.100 // 100 ms target time
- var v = 0.25 // volume
- function energy(channel) {
- var c = [405, 4095];
- var e = [29.5503, 3628.78];
- return e[0] + (e[1] - e[0]) * (channel - c[0]) / (c[1] - c[0]);
- };
- var f = energy(freq);
- osc.frequency.setTargetAtTime(f, ac.currentTime + d[0] * t, d[0] * t);
- gn.gain.setTargetAtTime(v, ac.currentTime + d[1] * t, d[1] * t);
- gn.gain.setTargetAtTime(0.0, ac.currentTime + d[2] * t, d[2] * t);
- }
- })()
- };
-
- function connect() {
- if (ctx.proto == 'binary') {
- ctx.ws = new WebSocket(ctx.url, 'binary');
- ctx.ws.binaryType = 'arraybuffer';
- } else {
- ctx.ws = new WebSocket(ctx.url);
- }
- ctx.ws.onerror = function(evt) {
- console.log('WS ERROR: ' + evt.data);
- };
- ctx.ws.onopen = function(evt) {
- console.log('WS OPENED: ' + ctx.url)
- };
- ctx.ws.onmessage = function(evt) {
- //console.log('WS DATA:');
- ctx.buffer += ctx.proto == 'binary' ? String.fromCharCode.apply(null, new Uint8Array(evt.data)) : evt.data;
- var rows = ctx.buffer.split(ctx.separatorRows);
- ctx.buffer = rows.pop(); // buffer incomplete rows
- for (var r = 0; r < rows.length; r++) {
- var cols = rows[r].split(ctx.separatorCols);
- var row = [];
- for (var c = 0; c < cols.length; c++) {
- row.push((function(str) {
- var num = parseFloat(str);
- c > 0 ? ctx.ping(num) : true;
- return isNaN(num) ? str : num;
- })(cols[c]));
- }
- if (ctx.headers.length == 0) {
- ctx.headers = row;
- } else {
- ctx.data.push(row);
- }
- }
- };
- ctx.ws.onclose = function(evt) {
- console.log('WS CLOSED:' + evt.code);
- window.setTimeout(connect, 1000); // retry every 1000 ms
- };
- }
- connect();
- this.getHeaders = function() { // get headers strings array
- return ctx.headers;
- };
- this.getData = function(len) { // get len datasets and delete old data
- return ctx.data.splice(0).slice(-1 * (len || 1));
- };
- return this;
- }
-
-
- function makeData(src, len) {
- //console.log('makeData(src,' + len + ')');
- len = len || 100;
- var rows = src.getData(len);
- var cols = src.getHeaders();
- var data = [];
- var vals = [];
- for (var c = 0; c < cols.length; c++) {
- vals = [];
- for (var r = 0; r < rows.length; r++) {
- var val = typeof(rows[r][c]) != 'undefined' ? rows[r][c] : null
- //console.log( cols[c] + '[' + r + '] = ' + val);
- vals.push(val);
- }
- data.push(vals);
- }
- var time = data && data.length > 0 ? data.shift() : null;
- return {
- 'x': (function(n) {
- for (x = []; n--; x.push(time));
- return x
- })(cols.length - 1),
- 'y': data,
- 'i': (function(n) {
- for (a = []; n--; a[n] = n);
- return a;
- })(cols.length - 1)
- };
- }
-
-
- function formatNames(str, find, replace) {
- str = str.toString();
- find = find || '_';
- replace = replace || ' ';
- return str.replace(
- new RegExp(
- find.replace(
- new RegExp(
- '([.*+?^=!:${}()|\[\]\/\\])',
- 'g'
- ),
- '\\$1'
- ),
- 'g'
- ),
- replace
- );
- }
-
-
- function makePlot(ctx, hdr) {
- //console.log(JSON.stringify(hdr, null, "\t"));
- var data = [];
- var cmap = chroma.scale(chroma.bezier(ctx.data.line.color)).mode('lab').correctLightness(false).colors(hdr.length - 1, null);
- for (var i = 1; i < hdr.length; i++) {
- var trace = JSON.parse(JSON.stringify(ctx.data))
- trace['type'] = 'scatter';
- trace['mode'] = 'lines';
- trace['fill'] = 'tonexty'; // 'tozeroy';
- trace['connectgaps'] = true;
- trace['name'] = formatNames(hdr[i]);
- trace['x'] = new Array(ctx.maxPoints);
- trace['y'] = new Array(ctx.maxPoints);
- trace['line']['width'] = 1;
- trace['line']['color'] = cmap[i - 1].css();
- trace['fillcolor'] = cmap[i - 1].alpha(0.2).css()
- data.push(trace);
- }
- document.title = ctx.layout.title;
- 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
- }
-
-
- function makeConfig(id, ctx) {
- var cmap = chroma.bezier(ctx.colors.graph).scale().colors(5);
- var data = {
- 'name': 'trace', // replaced by tsv column headers
- 'type': 'scatter',
- 'mode': 'lines', // 'lines+markers',
- 'line': {
- 'shape': ctx.shape,
- 'smoothing': ctx.smoothing,
- 'color': ctx.colors.lines, // interpolated by colormap function
- 'width': ctx.size
- },
- 'marker': {
- 'color': cmap[2],
- 'size': 1.5 * ctx.size
- },
- 'y': [0] // replaced by tsv column data
- };
- var layout = {
- 'title': ctx.title.plot,
- 'titlefont': {
- 'family': 'Courier New, monospace',
- 'size': 24,
- 'color': cmap[0]
- },
- 'autosize': true,
- // 'width': window.innerWidth,
- // 'height': window.innerHeight,
- 'plot_bgcolor': cmap[4],
- 'paper_bgcolor': cmap[4],
- 'xaxis': {
- 'title': ctx.title.xaxis,
- 'range': [0, 3650],
- 'color': cmap[1],
- 'titlefont': {
- 'family': 'Courier New, monospace',
- 'size': 18,
- 'color': cmap[0]
- },
- 'tickwidth': ctx.size,
- 'tickcolor': cmap[1],
- 'linecolor': cmap[1],
- 'gridcolor': cmap[3],
- 'zerolinecolor': cmap[1],
- 'mirror': true
- },
- 'yaxis': {
- 'title': ctx.title.yaxis,
- 'type': 'log',
- 'autorange': true,
- 'color': cmap[1],
- 'titlefont': {
- 'family': 'Courier New, monospace',
- 'size': 18,
- 'color': cmap[0]
- },
- 'tickwidth': ctx.size,
- 'tickcolor': cmap[1],
- 'linecolor': cmap[1],
- 'gridcolor': cmap[3],
- 'zerolinecolor': cmap[1],
- 'mirror': true
- },
- 'margin': {
- 'l': 64,
- 'r': 32,
- 'b': 64,
- 't': 80,
- 'pad': 0
- }
- };
- var config = {
- 'scrollZoom': true,
- 'showLink': false,
- 'displaylogo': false,
- 'modeBarButtonsToAdd': [
- {
- 'name': 'Toggle Lin/Log',
- 'click': function(gd) {
- Plotly.relayout(gd, 'yaxis.type', gd.layout.yaxis.type == 'log' ? 'linear' : 'log');
- },
- 'icon': {
- 'width': 1000,
- 'ascent': 850,
- 'descent': 0,
- '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'
- }
- }
- ],
- 'mmodeBarButtonsToRemove': ['sendDataToCloud']
- };
- return {
- 'id': id,
- 'data': data,
- 'layout': layout,
- 'config': config,
- 'maxPoints': ctx.maxPoints
- };
- }
-
-
- function createGraph(id, ctx) {
- ctx = { // sane defaults
- 'src': {
- 'stream': ctx.src.stream || '/data/stream.ws',
- 'init': ctx.src.init || '/data/hist.tsv'
- },
- 'title': {
- 'plot': ctx.title.plot || 'Plot Title',
- 'xaxis': ctx.title.xaxis || 'Time / s',
- 'yaxis': ctx.title.yaxis || 'Measured Entity / a.u.'
- },
- 'colors': {
- 'graph': ctx.colors.graph || ['black', 'white'],
- 'lines': ctx.colors.lines || ['blue', 'cyan', 'lightgreen', 'yellow', 'red']
- },
- 'maxPoints': ctx.maxPoints || 100,
- 'size': ctx.size || 2,
- 'scale': ctx.scale || 1.5,
- 'shape': ctx.shape || 'linear',
- 'smoothing': ctx.smoothing || 1
- };
-
- var src = dataSrc([
- location.protocol == 'https:' ? 'wss://' : 'ws://',
- location.hostname,
- location.protocol == 'https:' ? ':' + (localtion.port || 443) : ':' + (location.port || 80),
- ctx.src.stream
- ].join(''));
-
- var setup = window.setInterval(function() { // wait for headers to arrive
- var headers = src.getHeaders();
- if (headers.length > 0) {
- clearInterval(setup);
- makePlot(makeConfig(id, ctx), headers);
-
- function loadInitData(dataUrl) {
- var xmlhttp = new XMLHttpRequest();
- xmlhttp.open("GET", dataUrl); // xmlhttp.open("GET", url, false);
- xmlhttp.onreadystatechange = function() {
- if ((xmlhttp.status == 200) && (xmlhttp.readyState == 4)) {
- var maxPoints = ctx.maxPoints || 4096;
-
- function energy(channel) {
- var c = [405, 4095];
- var e = [29.5503, 3628.78];
- return e[0] + (e[1] - e[0]) * (channel - c[0]) / (c[1] - c[0]);
- };
-
- var data = {
- 'x': [(function(n) {
- for (a = []; n--; a[n] = energy(n));
- return a;
- })(maxPoints)],
- 'y': [
- []
- ],
- 'i': [],
- 't': 0,
- 'e': 0
- };
-
- var initData = xmlhttp.responseText.split("\n").slice(0, maxPoints).map(parseFloat);
- for (i = 0; i < maxPoints; i++) { // prefill initial data
- var val = initData[i];
- if (isNaN(val)) {
- data.y[0][i] = 0;
- } else {
- data.y[0][i] = val;
- data.e += val;
- }
- }
- //console.log(data);
-
- var res = true;
- res &= Plotly.relayout(id, {
- 'title': ctx.title.plot + ' ' + data.t + 's',
- 'yaxis.title': ctx.title.yaxis + ' / ' + data.e
- }).catch(function(e) {
- //console.log('ERROR[Plotly.relayout()]: ' + e);
- });
-
- res &= Plotly.extendTraces(id, {
- 'x': data.x,
- 'y': data.y
- }, [0], maxPoints).catch(function(e) {
- //console.log('ERROR[Plotly.extendTraces()]: ' + e);
- });
-
- var update = window.setInterval(function() { // update plot in realtime
-
- var rawdata = makeData(src, 1024 * 1024 * 1024); // get at most 1 GB of events
-
- data.i = (function(n) {
- for (a = []; n--; a[n] = n);
- return a;
- })(rawdata.y.length);
-
- for (i = 0; i < rawdata.y.length; i++) {
- for (j = 0; j < rawdata.y[i].length; j++) {
- data.y[i][rawdata.y[i][j]] += 1;
- data.e += 1
- }
- }
-
- var evtTime = rawdata.x[0][rawdata.x[0].length - 1];
- data.t = evtTime ? evtTime : data.t;
-
- return data.i.length > 0 ? (function() {
- var res = true;
- res &= Plotly.relayout(id, {
- 'title': ctx.title.plot + ' ' + data.t + 's',
- 'yaxis.title': ctx.title.yaxis + ' / ' + data.e
- }).catch(function(e) {
- //console.log('ERROR[Plotly.relayout()]: ' + e);
- });
-
- res &= Plotly.extendTraces(id, {
- 'x': data.x,
- 'y': data.y
- }, data.i, maxPoints).catch(function(e) {
- //console.log('ERROR[Plotly.extendTraces()]: ' + e);
- });
- return res;
-
- })() : true;
- }, 500); // time resolution 500ms
- }
- };
- xmlhttp.send();
- }
- loadInitData(ctx.src.init);
- }
- }, 500);
- }
|