Ember.js + Sparkline: Touch-Driven Real-Time Graph Experiment

I was messing around, integrating a couple of data visualization libraries into an ember project and thought it would be fun to create a live-updating heartbeat-style graph whose data was bound to the oscillating movements of my finger on a touch screen.

I looked around for a bit and saw that jquery sparkline had a little demo that would pulse up and down with the measurement of the user’s mouse speed. That looked pretty cool, so I set about creating a similar touch-based real-time graph “the ember way.”

Today, I’ll quickly take you through it.

First we add jquery-sparkline to our js manifest file

//= require vendor/jquery.sparkline

Now let’s pause a second and think this through. When a user moves his finger over a specific view, we want to capture that touchMove event, generate a numeric value that tells us where the finger is on the screen at any given time and bind a controller property to that value.

We can then write a method that samples that controller value repeatedly at steady intervals of time to generate the data set that will feed the graph.

Sound good? Let’s dig in!

Let’s create a realTimeGraph entry in router.js, along with a controller, view and template that will represent the container view that houses the graph. We will also create a SparkLineView that represents the actual graph.

this.route('realTimeGraph');

App.RealTimeGraphController = Em.ArrayController.extend({ // this is where we want to set the value of a variable

// that represents the touch/mouse movement });

App.RealTimeGraphView = Ember.View.extend({ templateName: 'real_time_graph', // this is where we want to track the user's touchMove/MouseMove

// anywhere on the device screen });

real_time_graph.emblem template:

= view App.SparklineView

App.SparklineView = Ember.View.extend({ // this is the actual graph view });

First, let’s get the graph to show up with some hardcoded data:

App.SparklineView = Ember.View.extend({ classNames: ['sparkline'], data: [1,6,10,3,9,8], didInsertElement: function(){ var data = this.get('data'); this.$().attr('values', data); this.$().sparkline(data, {type:"line", barWidth:"5", lineWidth:"2", width:"100", lineColor:"#0d77b6", fillColor:"#80B9DB", highlightLineColor:"#afdefa", spotRadius:"5", width:"300", height:"60"}); } });

OK, now we make it dance!

Let’s capture and normalize mouseMove and touchMove events using a method from this stackoverflow post and combine the event’s pageX and pageY values to calculate the distance your finger (or mouse) is away from the top left of the screen. We’ll then set that value to a controller variable whenever your finger (or mouse) moves:

App.RealTimeGraphView = Ember.View.extend({ templateName: 'real_time_graph', mouseMove: function(event) { var mouseDistance = 0; var mousex = event.pageX; var mousey = event.pageY; mouseDistance += Math.max( Math.abs(mousex), Math.abs(mousey) ); this.set('controller.mouseDistance', mouseDistance); }, touchMove: function(event) { event.preventDefault(); this.mouseMove(App.normalizeTouchEvent(event)); } });

App.normalizeTouchEvent = function(event) { if (!event.touches) { event.touches = event.originalEvent.touches; } if (!event.pageX) { event.pageX = event.originalEvent.pageX; } if (!event.pageY) { event.pageY = event.originalEvent.pageY; }

return event; };

Now let’s change our SparklineView to sample the mouseDistance controller variable every 0.3 seconds, and then cancel the next sample loop if the view is destroyed:

App.SparklineView = Ember.View.extend({ classNames: ['sparkline'], data: [1,2], didInsertElement: function() { this.sample(); }, sample: function() { var nextSample = Ember.run.later(this, function() { var data=this.get('data'); this.$().attr('values', data); this.$().sparkline(data, {type:"line", barWidth:"5", lineWidth:"2", width:"100", lineColor:"#0d77b6", fillColor:"#80B9DB", highlightLineColor:"#afdefa", spotRadius:"5", width:"300", height:"60"}); var mouseDistance = this.get('controller.mouseDistance'); //data.shift drops the first array object if array length>15 if (data.length > 15) { data.shift(); } data.push(mouseDistance); this.sample(); }, 300); this.set('nextSample', nextSample); }, willDestroyElement: function() { var nextSample = this.get('nextSample'); Ember.run.cancel(nextSample); } });

And Voilà! We have real-time touch-driven goodness with very little code.

(*note - I decided to focus on distance instead of jquery sparkline’s speed calculation in order to make the code more readable and easier to follow in a quick blog post.)