Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

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
You can’t perform that action at this time.