Skip to content

Instantly share code, notes, and snippets.

@cuadue
Created September 3, 2013 17:43
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save cuadue/6427101 to your computer and use it in GitHub Desktop.
Save cuadue/6427101 to your computer and use it in GitHub Desktop.
Streaming data to cubism.js with websockets
<!DOCTYPE html>
<meta charset='utf-8'>
<head>
<title>Cubism + Websockets</title>
<script language='javascript' src='d3.min.js'></script>
<script language='javascript' src='cubism.v1.js'></script>
<script language='javascript'>
/* I can never seem to remember:
Array.push() appends to the end, and returns the new length
Array.pop() removes the last and returns it
Array.shift() removes the first and returns it
Array.unshift() appends to the front and returns the new length
*/
$(function() {
function json_ws(path, on_msg) {
if (!("WebSocket" in window)) {
alert("Use a browser supporting websockets");
}
var sock = new WebSocket("ws://" + location.host + path);
sock.onmessage = function(msg) {
var data;
try {
data = JSON.parse(msg.data);
}
catch (SyntaxError) {
console.log("Invalid data: " + msg.data);
return;
}
if (data)
on_msg(data);
}
window.onbeforeunload = function() {
sock.onclose = function() {};
sock.close();
}
}
var analog_keys = ['vcell', 'current', 'temp'];
(function() {
function make_realtime(key) {
var buf = [], callbacks = [];
return {
data: function(ts, val) {
buf.push({ts: ts, val: val});
callbacks = callbacks.reduce(function(result, cb) {
if (!cb(buf))
result.push(cb);
return result
}, []);
},
add_callback: function(cb) {
callbacks.push(cb);
}
}
};
var realtime = {
vcell: make_realtime('vcell'),
current: make_realtime('current'),
temp: make_realtime('temp'),
rawcap: make_realtime('rawcap'),
};
/* This websocket sends homogenous messages in the form
* {timestamp: 1234567, analog: {vcell: 3.3, current: 2.3, temp: 20}}
* where timestamp is a Unix timestamp
*/
json_ws("/realtime.ws", function(data) {
analog_keys.map(function (key) {
realtime[key].data(data.timestamp, data.analog[key]);
});
});
var context = cubism.context().step(1000).size(960);
var metric = function (key, title) {
var rt = realtime[key];
return context.metric(function (start, stop, step, callback) {
start = start.getTime();
stop = stop.getTime();
rt.add_callback(function(buf) {
if (!(buf.length > 1 &&
buf[buf.length - 1].ts > stop + step)) {
// Not ready, wait for more data
return false;
}
var r = d3.range(start, stop, step);
/* Don't like using a linear search here, but I don't
* know enough about cubism to really optimize. I had
* assumed that once a timestamp was requested, it would
* never be needed again so I could drop it. That doesn't
* seem to be true!
*/
var i = 0;
var point = buf[i];
callback(null, r.map(function (ts) {
if (ts < point.ts) {
// We have to drop points if no data is available
return null;
}
for (; buf[i].ts < ts; i++);
return buf[i].val;
}));
// opaque, but this tells the callback handler to
// remove this function from its queue
return true;
});
}, title);
};
['top', 'bottom'].map(function (d) {
d3.select('#charts').append('div')
.attr('class', d + ' axis')
.call(context.axis().ticks(12).orient(d));
});
d3.select('#charts').append('div').attr('class', 'rule')
.call(context.rule());
charts = {
vcell: {
title: 'Voltage',
unit: 'V',
extent: [2.4, 4.4]
},
current: {
title: 'Current',
unit: 'mA',
extent: [-5000, 2000]
},
temp: {
title: 'Temperature',
unit: '\u00b0C',
extent: [-20, 60]
}
};
Object.keys(charts).map(function (key) {
var cht = charts[key];
var num_fmt = d3.format('.3r');
d3.select('#charts')
.insert('div', '.bottom')
.datum(metric(key, cht.title))
.attr('class', 'horizon')
.call(context.horizon()
.extent(cht.extent)
.title(cht.title)
.format(function (n) {
return num_fmt(n) + ' ' + cht.unit;
})
);
});
context.on('focus', function (i) {
if (i !== null) {
d3.selectAll('.value').style('right',
context.size() - i + 'px');
}
else {
d3.selectAll('.value').style('right', null)
}
});
})();
});
</script>
<style>
html {
min-width: 1040px;
}
body {
font-family: 'Helvetica Neue', Helvetica, sans-serif;
width: 960px;
}
.horizon {
border-bottom: solid 1px black;
overflow: hidden;
border-top: solid 1px black;
position: relative;
}
.horizon + .horizon {
border-top: none;
}
.horizon .title,
.horizon .value {
bottom: 0;
line-height: 30px;
margin: 0 6px;
position: absolute;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
white-space: nowrap;
}
.horizon .title {
left: 0;
}
.horizon .value {
right: 0;
}
.horizon canvas {
display: block;
}
.axis {
font: 10px sans-serif;
}
.axis text {
transition: fill-opacity 250ms linear;
}
.axis path {
display: none;
}
.line {
/* No idea why 8px margin left is needed here :( */
margin-left: 8px;
background: black;
opacity: 0.2;
z-index: 2;
}
.axis .line {
stroke: black;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id='charts'></div>
</body>
</html>
@yosiat
Copy link

yosiat commented Jan 9, 2016

Hi @cuaude,
I have used your example to do something similar (getting data using rx/autobahn) and It really helped me!

https://github.com/yosiat/pubsub_perf

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment