Experimenting with flot.js, a flexible chart library

/ 3 Comments

Ever since the introduction of the canvas element, generating charts has been one of it’s most common use cases. There are numerous javascript chart libraries, some of which are fairly mature. My experience of these libraries is that the default styling is pretty offensive but they are otherwise full of features.

So, having heard a lot about flot.js, I finally have a need to play with charts. In this overview we will take a look at whats possible in terms of functionality and custom styling

You can download the source or view the demo at the bottom of this post but here’s an image of the end result:

chart

Lets take a quick look at the directory structure we’ll be using. We’re going to strip out any scripts from the flot library that we don’t need:

index.html // blank html file that includes our scripts
js/chart.js // what we'll be writing
js/lib/jquery.min.js // dependency
js/lib/flot/excanvas.min.js // for browsers that don't support canvas
js/lib/flot/jquery.flot.js // the flot library itself

The index.html file isn’t going to contain any markup. We will generate our placeholder div and styles on the fly.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Flot Experiment</title>
</head>
<body>
    <!--[if lte IE 8]
    ><script language="javascript" type="text/javascript" src="js/lib/flot/excanvas.min.js"></script>
    <![endif]-->
    <script src="js/lib/jquery.min.js"></script>
    <script src="js/lib/flot/jquery.flot.js"></script>
    <script src="js/chart.js"></script>
</body>
</html>

js/chart.js has 3 main areas of interest:

  1. A placeholder object that contains properties relating to the div that contains the canvas
  2. An options object that contains chart options that’s passed as an argument when we call $.plot()
  3. A methods object that contains methods to generate random data, bundle it up in to one big array of data sets, create an HTML element and initiate the flot plugin by passing all the necessary arguments

I’ve gone a little over board on the comments, forgive me if some of them state the obvious

// wrap in self-executing anonymous function
$(function() {

// create namespace
var Chart = {

    // how many lines to display on grid
    numLines: 3,

    // options for html element that contains chart canvas
    placeholder: {

        id: 'placeholder',

        styles: {
            width: '565px',
            height: '230px',
            margin: '30px',
            position: 'relative',
            font: '14px Helvetica, Arial, sans-serif'
        }

    },

    // chart options
    options: {

        // each new line on graph will use the next color in this array
        colors: ['#43729e', '#9e4343', '#659e43', '#dcdb60'],

        grid: {
            aboveData: true, // grid has the greater z-index
            labelMargin: 5, //px margin of axis labels
            borderWidth: 0, // hide wrapping border
            hoverable: true, // highlight point on mouseover
            mouseActiveRadius: 5 // the size of the mouseover effect
        },

        // series options are applied to every data series that's added
        series: {
            lines: {
                show: true,
                fill: true, // fill area below the line
                lineWidth: 3,
            },

            points: {
                show: true,
            }
        },

        xaxis: {
            mode:'time',
            ticks: 12, // number of intervals
            // frequency of interval and unit of measurement
            minTickSize: [1, 'month'],
            min: (new Date('2012/01/01')).getTime(), // starting date
            max: (new Date('2012/12/01')).getTime() // end date
        },

        shadowSize: 0 // no drop shadow on lines or points

    }, // end options

    methods: {

        init: function() {
            // get chart options
            var id          = Chart.placeholder.id,
                styles      = Chart.placeholder.styles,
                numLines    = Chart.numLines,
                data        = Chart.methods.generateChartData(numLines),
                options     = Chart.options;

            // create div
            $('body').append('<div id="' + id + '">');

            // add styles to chart placeholder div
            $('#' + id).css(styles);

            // create chart
            $.plot($('#' + id), data, options);

            // debug chart data
            console.log(JSON.stringify(data));
        },

        // generate a random number with a range of -99 to 99
        randNum: function() {
            var num = Math.round(Math.random()*198) - 99;
            return num;
        },

        generateChartData: function(numOfDataSets) {
            var singleCoordinateArr = [],
                coordinateGroupArr = [],
                seriesArr = [],
                dateStr,
                randNum;

            for (var i=0; i<numOfDataSets; i++) {

                for (var j=0; j<12; j++) {

                    // create date string for each month
                    dateStr = '2012/' + (j+1) + '/01';

                    // use the date string to create a timestamp
                    timestamp = (new Date(dateStr)).getTime();

                    // generate a random co-ordinate for the y-axis
                    randNum = Chart.methods.randNum();

                    // create a single co-ordinate array
                    singleCoordinateArr.push(timestamp);
                    singleCoordinateArr.push(randNum);

                    // create array containing grouped co-ordinates
                    coordinateGroupArr.push(singleCoordinateArr);

                    // reset array
                    singleCoordinateArr = [];

                }

                // create array containing a series of grouped co-ordinates
                seriesArr.push(coordinateGroupArr);

                // reset array
                coordinateGroupArr = [];

            }
            return seriesArr;

        }

    } // end methods

}; // end Charts namespace

// kick things off
Chart.methods.init();

});

The only real complexity exists within the Chart.methods.generateChartData(). Here we need nested for loops. The first creates the necessary number of data sets, while the inner for loop creates an an array of co-ordinate arrays.

In this instance it means that we can generate random data every time the page is loaded. This may or may not be useful for testing various data within the contraints of our chart options. However, it means that I simply re-write that method to get real application data.

View Demo

Download source files

I’ll do a follow up post when I implement this chart in to a betting application that I’m working on but hopefully this post offers some insight in to how flexible the flot.js library is.

3 Comments

  1. Dave Kingsnorth says:

    Hi Manish, I haven’t tried myself. I tend to use Bower as a package manager and I noticed that flot.js doesn’t have an ‘-amd’ option like backbone and underscore. Specifically what problem are you having?

  2. jay says:

    Hi Dave,
    Is it possible to attach a js function on click of x-axis tick ( all the month names here)
    Appreciate any help. Thanks.

Leave a Comment