La séptima vida

...o el gato así lo espera/teme

Plotting data with Server-Sent Events

Yesterday, I imagined a machine that could publish data using the MQTT protocol. These data was then made accessible to a webpage with server-side events, a technology that allows servers to push information to clients. However, the client side was so simple that it just printed numbers to the console.

In this article, I take an example from the Flot JavaScript plotting library and modify it to work with the same script as yesterday. You can see the original example here.

We are starting from an example that already works. It builds a function that generates random numbers, builds a plot, and declares another function to update the graph. There is also some logic that builds a timer and updates the chart periodically. Since we will be sending the values that will be plotted via our server-side events, we do not need the random number generator nor the timer logic. However, the random generator function defines the format for our data:

var data = [], totalPoints = 300;

function getRandomData() {

    if (data.length > 0)
        data = data.slice(1);

    // Do a random walk

    while (data.length < totalPoints) {

        var prev = data.length > 0 ? data[data.length - 1] : 50,
            y = prev + Math.random() * 10 - 5;

        if (y < 0) {
            y = 0;
        } else if (y > 100) {
            y = 100;
        }

        data.push(y);
    }

    // Zip the generated y values with the x values

    var res = [];
    for (var i = 0; i < data.length; ++i) {
        res.push([i, data[i]])
    }

    return res;
}

In line 26 we see that data is put into an array, called ref. The for loop that follows puts [x,y] pairs into ref. In those pairs, x is the index number and y is the corresponding number from the data array. It is this array that holds the data that is plotted. So that's the format we need: An array of [x, y] arrays.

The actual plotting is done in the following piece of code:

var plot = $.plot("#placeholder", [ getRandomData() ], {
    series: {
        shadowSize: 0	// Drawing is faster without shadows
    },
    yaxis: {
        min: 0,
        max: 100
    },
    xaxis: {
        show: false
    }
});

function update() {

    plot.setData([getRandomData()]);

    // Since the axes don't change, we don't need to call plot.setupGrid()

    plot.draw();
    setTimeout(update, updateInterval);
}

We need to get rid of the getRandomData call in line 1, and of line 21, where it sets a timer, setTimeout(update, updateInterval). But the rest will work as-is.

In order to receive data points from our server, we need to declare our event source and to listen to the events that we will be sending, data and closed.

var es = new EventSource('http://0.0.0.0:5000');
es.addEventListener('data', function(event) {
    update(plotData, event.data);
});
es.addEventListener('closed', function(event) {
    console.log(event);
    es.close();
});

And of course, we need to masseage the data array so that we get it in the appropriate form. Our plot will contain 100 points which will be updated as time goes by. Our full script looks like this:

$(function() {

    var plotData = [0];
    var data = [];

    var plot = $.plot("#placeholder", [data], {
        series: {
            shadowSize: 0	// Drawing is faster without shadows
        },
        yaxis: {
            min: 0,
            max: 100
        },
        xaxis: {
            show: false,
            min: 0,
            max: 100
        }
    });
    
    function update(plotData, newPoint) {
        
        // Push new point into plot data
        plotData.push(newPoint);
        if (plotData.length > 100)
            plotData.shift();
        
        var i = 0;
        data = [];
        plotData.forEach(function(item, index, array) {
            data.push([i, Number(item)]);
            i++;
        });
        
        console.log(JSON.stringify(data));

        plot.setData([data]);

        // Since the axes don't change, we don't need to call plot.setupGrid()

        plot.draw();
    }


    var es = new EventSource('http://0.0.0.0:5000');
    es.addEventListener('data', function(event) {
        update(plotData, event.data);
    });
    es.addEventListener('closed', function(event) {
        console.log(event);
        es.close();
    });

    // Add the Flot version string to the footer

    $("#footer").prepend("Flot " + $.plot.version + " – ");
});

By substituting the JavaScript in the original example, and starting the unchanged application from the previous article, we obtain our nice real-time graph:

Browser screen shot