Javascript time series plotting recipe

09 Feb 2013

Plotting with jQuery and Flot

This is a recipe for plotting a time series using a dead-simple graphics library for Javascript called Flot.

While it might lack the cool-factor of d3.js, Flot is extremely easy to use and has lots of features. It also makes some great looking plots!

This is what we’re making:

live updates to Flot Time Series plot using socket.io

Here’s the live demo deployed on nodejitsu as well.

series.js

We’ll use both Moment.js and Moment-Range, two awesome libraries for dealing with dates. These are basically the javascript equivalents to lubridate for R.

var moment  = require('moment-range')
    , _     = require('underscore');

var Series = function () {
    return { create: create,
             randint: randint };

    // generates random int b/w min & max
    function randint (min,max) {
        return Math.floor( Math.random() * (max - min + 1)) + min ;
    }

    // fake data for y-axis
    function get_measure (n) {
        var n_samples = n
            , m =[];

        _.each(_.range(n_samples), function (index, day) {
            m.push(randint(800 , 1200 ))
        })
        return m
    }

    // array of dates in milliseconds
    // for the last 30 days
    function get_dates () {
        var today     = moment()
            , start   = moment(today).subtract('days', 30)
            , range   = moment().range(start, today)
            , dates   = [];
      
        range.by('d', function (m) {
            dates.push(m.valueOf())
        });
        return dates
    }

    // multidimensional array w/ shape (30, 2)  
    // [[1357788897349, 3932], [1358048097349, 1063]]
    function create () {
        var dates = get_dates()
            , measures = get_measure(dates.length)
        
        // underscore _.zip() to
        // merge lists a la python
        return _.zip(dates, measures)
    }
}();

module.exports.Series = Series

index.html

Here’s the client side code. I’m omitting the html markup and just showing the javascript just to keep it short.

(function (d, b) {

    function scale_grid_week (axes) {

        var ax = axes
          // shade weekends on grid layout
          , markings = []
          
          // start xaxis on the Saturday prior to 1st observation in our data
          , xmin = moment(ax.xaxis.min).day(-7)
          , xmax = moment(ax.xaxis.max)
          , one_week = moment.duration(7, 'days').asMilliseconds()
          , weekend = moment.duration(2, 'days').asMilliseconds();

        for (i = xmin._i; i < xmax._i; i += one_week) {
          var week = {
              xaxis: {
                  from: i ,
                  to: i + weekend
              }
          }
          markings.push(week)
        }
        return markings
    }
    
    function bind_events () {
    
        var socket = io.connect();
        // var socket = io.connect('http://localhost');

        socket.on('plot data', function (d) {
            draw(d)
        });

        function draw (plt) {

            // Flot has lots of cool options
            var options = {
                    series: {
                        lines: { 
                            show: true, 
                            fill: true, 
                            lineWidth: 1, 
                            steps: false, 
                            fillColor: { colors: [{opacity: 0.25}, {opacity: 0}] },
                            hoverable: true
                        },
                        points: { 
                            show: true, 
                            radius: 2.5, 
                            fill: true
                        }
                },
                legend: { 
                    position: "se"
                },
                xaxis: {
                    mode: "time",
                    tickLength: 5
                },
                tooltip: true,
                tooltipOpts: {
                    content: '%s: %y'
                },
                selection: {
                    mode: "x"
                },
                grid: {
                    hoverable: true,
                    borderWidth: 2,
                    // pass function for some opts
                    markings: scale_grid_week
                }
            };

            // plt looks like this:
            // [{label: "some label", 
            //   data: [[13579609517031, 953], [13579609517031, 953],...]}]
            var plot = $.plot("#placeholder", plt, options);
        }
    }

    $(document).ready(function (){
        bind_events()
    });
  })(jQuery, this)

app.js

And finally, here’s the server.

var app     = require('express')()
  , server  = require('http').createServer(app)
  , io      = require('socket.io').listen(server)
  , Series  = require('./series').Series;

app.use(require('connect').static(__dirname + '/public'))
server.listen(8080);

app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

function make_series() {
    var series = [{ 
        data: Series.create(),
        label: "Series " + Series.randint(1,10).toString()
      }];

    return series
};

io.sockets.on('connection', function (socket) {
    socket.emit('plot data', make_series() );

    // push new data every 2 sec
    setInterval(function(){
        socket.emit('plot data', make_series() );
    }, 2000);

});

Other resources: