On Github rluta / d3-realtime
Raphaël Luta - @raphaelluta
#bestofweb2015
function update(objectArray) { var duration = this.options('transition'); var height = this.height()-1; var x = this.options('x'); var y = this.options('y'); xScale.domain(objectArray.map(x)); yScale.domain([0, d3.max(objectArray, y)]); axisX.call(xAxisFn); axisY.call(yAxisFn); var selectedData = svg.selectAll('.bar').data(objectArray, x); selectedData.enter() .append('rect') .attr('class', 'bar') .attr('width', xScale.rangeBand()) .attr('x', function(d) { return xScale(x(d)) }) .attr('y', height) .attr('height', 0); selectedData.transition().duration(duration) .attr("x", function(d) { return xScale(x(d)) }) .attr("y", function(d) { return yScale(y(d)) }); .attr("height", function(d) { return height - yScale(y(d)) }); selectedData.exit().transition().duration(duration) .attr('x', function(d) { return xScale(x(d)) }) .attr('y', height) .attr('height', 0) .remove(); }
var timerId = null; $(mySection).on('start', function (event, args) { if (timerId === null) { timerId = setInterval(function () { data = refreshData() chart.update(data) },args.rate) } }) $(mySection).on('stop', function () { if (timerId !== null) { clearInterval(timerId); timerId = null; } })
function updateDashboard(addMore,max) { for (var i=0; i < addMore; i++) { var order = genOrder(); orders.push(order); if (orders.length > max) orders.shift(): var current = counts.get(order.customer) || 0; counts.set( order.customer, Math.round(current+order.price*order.quantity) ); } total = d3.sum(orders,function (d) { return d.price*d.quantity }); datatable.update(orders); gauge.update(total); customerBar.update(counts.topN(5)); }
function update(objectArray) { var colnames = []; if (mydata != null && mydata.length > 0) { colnames = d3.keys(objectArray[0]) } var th = thead.selectAll('th').data(colnames,identity) th.enter().append('th') th.text(identity); th.exit().remove(); var tr = tbody.selectAll('tr').data(objectArray, opts.id) tr.enter().append('tr') tr.exit().transition().duration(200).remove() var cells = tr.selectAll('td').data(d3.values) cells.enter().append('td') cells.text(identity); cells.exit().transition().duration(200).remove(); }
this._update = function(objectArray) { var duration = this.options('transition'); pointer.transition().duration(duration) .attr('transform', 'rotate(' +angle(objectArray) +')'); value.text(valueFormat(objectArray)); }; this._render = function (objectArray) { var center = 'translate('+radius +','+ radius +')'; var colorFn = this.options('arcColorFn'); var svg = this.svg().append('g').attr('class', 'gauge') .attr('width', this.width()) .attr('height', this.height()); var arcs = svg.append('g').attr('class', 'arc').attr('transform', center); arcs.selectAll('path').data(tickData) .enter().append('path') .attr('fill', function(d, i) { return colorFn(d * i); }) .attr('d', arc); var labelMargin = this.options('labelMargin'); var labels = svg.append('g').attr('class', 'label').attr('transform', center); labels.selectAll('text').data(ticks) .enter().append('text') .attr('transform', function(d) { return 'rotate(' +angle(d) +') translate(0,' +(labelMargin - radius) +')'; }) .text(this.options('labelFormat')); var vg = svg.append('g').attr('class', 'value').attr('transform', center); value = vg.append('text').attr('transform', 'translate(0,-'+radius/3+')') var pointerWidth = this.options('pointerWidth'); var pointerTailLength = this.options('pointerTailLength'); var lineData = [ [pointerWidth / 2, 0], [0, -pointerHeadLength], [-(pointerWidth / 2), 0], [0, pointerTailLength], [pointerWidth / 2, 0] ]; var pointerLine = d3.svg.line().interpolate('monotone'); var pg = svg.append('g').data([lineData]) .attr('class', 'pointer') .attr('transform', centerTx); pointer = pg.append('path') .attr('d', pointerLine) .attr('transform', 'rotate(' +minAngle +')'); this._update(objectArray ? objectArray : 0); };
D3.js natively implements XHR to consume HTTP documents and connect to REST APIs
Of course Jquery, angular, etc.. work too
2 main tools:
Some libraries allow fallback to standard XHR requests: socket.io, socks.js, etc...
var socket = new WebSocket("ws://localhost:4242/eventbus"); socket.onmessage = function(event) { data.push(event.data); chart.update(data); } socket.onopen = function(event) { alert("Web Socket opened!"); }; socket.onclose = function(event) { alert("Web Socket closed."); }; function send(message) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { socket.send(message); } else { alert("The socket is not open."); } }
var worker = new Worker('js/data-collector.js'); worker.addEventListener('message',function(event) { gauge.update(event.data); },false); worker.postMessage('start');
var ws, queue = [], maxSize = 1000; function countAndSend(){ var count = queue.splice(0,queue.length) .reduce(function (prev,d) { return prev+d.price },0); postMessage(count); } self.addEventListener('message',function (evt) { if (evt.data == 'start') { ws = new WebSocket('ws://myserver/shopping-info') } ws.onmessage = function (evt) { queue.push(evt.data); if (queue.length > maxSize) queue.shift() } setInterval(countAndSend,1000); },false);
Goal: Amortize expensive tasks
Most expensive tasks in our vizualisation:
see also: Debounce functions
Detect congestion conditions and notify producer to reduce incoming events
If can't notify producer, drop events and notify UI
var lastRun = Date.now(), onHold = false; function refreshChart(timestamp) { window.requestAnimationFrame(refreshChart); if (timestamp - lastRun > 40) { worker.postMessage('hold'); onHold = true; } else if (onHold) { worker.postMessage('resume'); } lastRun = timestamp; chart.update(data); };
state.eb.registerHandler('votes', function (message) { state.queue.push(message); state.display.push(message); if (state.display.length > 5) state.display.shift(); if (message.answer) { state.counts.set(message.answer, (state.counts.get(message.answer) || 0) + 1); state.total += 1; } if (state.queue.length > config.discard) { state.queue.shift(); state.lost += 1; state.eb.send('control','pause', function (reply) { state.onHold = true; }) } if (onHold && state.queue.length < config.discard * 0.8) { state.eb.send('control','resume', function (reply) { state.onHold = false; }) } });
Use D3.js with a <canvas> element
Epoch.js: A visualization library over D3.js for realtime dashboards by Fastly
Streamdata.io: Service to convert a REST API to a streaming API
Available on Github: http://rluta.github.io/d3-realtime
If you enjoyed this presentation, check out Brown Bag Lunch Francehttp://www.brownbaglunch.fr/
Church of Batman the Redeemer is by Terry Gilliam - The Zero Theorem
Batman vs Joker Injustice, Gods Among Us Lego Kryptonite Brick Filmers Guild Mario and Yoshi Nintendo and Geo's world Wiki