Sunday, January 17, 2016

How to make a dashboard with Tufte's sparklines in d3.js


Thus far, we've used traditional charts to visualize our data. We will now be adding a visualization that is less common: a sparkline. What is a sparkline?

    A sparkline is a small intense, simple, word-sized graphic with typographic resolution.
    Sparklines mean that graphics are no longer cartoonish special occasions with captions and boxes,
    but rather sparkline graphics can be everywhere a word or number can be: embedded in a sentence,
    table, headline, map, spreadsheet, graphic. Data graphics should have the resolution of typography.
    -- Edward Tufte, Beautiful Evidence

Sparklines can be very handy for quickly analyzing several time series at a time. In this blog post, I'll explain how you can create a dashboard using `d3.js` that visualizes sparklines of currency fluctuations. This dashboard not only visualizes currency fluctuations over the past month for major currencies, it also updates in real time.


You can view a live version of the dashboard at http://sparklines-dash.rowanv.com.

Why Visualize Currency Exchange Rates using Sparklines?

So first off, what prompted me to make this dashboard? I was looking at the exchange rates put out by the European Central Bank. The ECB publishes exchange rates for a number of major currencies each day. However, their interface misses out on a great opportunity to use sparklines:



While they show a small arrow that illustrates the derivative of each of the major currencies, their dashboard does not show the currency fluctuations over time. In order to view this type of a graph, one has to click through to the individual graphs by clicking on the 'Chart' icon. This prevents quick, side-by-side comparisons of the currency fluctuations over time. A quick peek at last month's currency fluctuations would be a great visualization for using sparklines in order to quickly compare the shapes of the time series. While I don't think that they completely replace the chart tool that the ECB has published, they'd be a nice complement to the data that they provide.


Getting the Data:

This merits its own blog post. For now, you can check out my iPython notebook here for a quick overview of the process.

Making the d3.js visualization:

After the data acquisition and cleaning process, I had a view within my web application which served a JSON response of my currency data. This data included time series for the past month for each of the major currencies.  If we visualize the data in a tabular format, it would look like this:


Each column corresponded to a currency type, and each row corresponded to a day of observations. With this data in hand, I created a basic HTML document that would read the data within d3:



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<html>
<meta charset='utf-8'>


    
    <link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'> <!--#1-->
    <style>
    text {
        font-family: 'Open Sans', sans-serif;
    }
    <!--#2-->
    .sparkline-container {
        position:absolute;
        top:0px;
        left:0;
        float:left;
        width:300px;
        height:100px;
    }
    <!--#3-->
    .sparkcircle {
      fill: #f00;
      stroke: none;
    }
    <!--#4-->
    path {
        stroke: #000;
        stroke-width: 0.35px;
        fill: none;
    }
    </style>


<body>
    <!--#5-->
    <div id="graph" class="aGraph sparkline-container" style=""></div>

</body>


<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.js" charset='utf-8'></script>

<script src='https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js'></script>
<script>
// # 6
$(document).ready(function() {


    d3.json('/data/not_realtime/currency_rates_month/', function(error, data) {
 
    });
});

</script>


</html>


Let's highlight some key things that our template is doing:
# 1: We specify the font that we will be using for the text next to our sparklines
# 2: We specify the width and height of our sparklines container
# 3: We specify how we'd like to format the little red circles at the end of the sparklines
# 4: Our sparklines will be 0.35px thick and use a black stroke colour
# 5: This is the actual div element which we will be using as our starting point for the visualization. We'll select this element and then attach all other visual elements to it.
# 6: We write our actual `d3.js` script between these `script` tags. So far, we've used a jQuery helper function to keep our `d3.js` script from running until the DOM is ready for it, and we've requested our data set from the URL at `/data/not_realtime/currency_rates_month/', where it will be served as a json response.

Actually Drawing Our First Sparkline

Now that we have our HTML template, we can draw our first sparkline. You can read through an example of how to draw an individual sparkline in my sparkline blogpost.


Reshaping Our Data
Based on the way that we have written our sparkline visualization function, it would be useful to reshape our data. It will be easiest to work with if we have a set of json objects with each key representing our currency country name and each value representing a list of time series values. We do some reshaping:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$(document).ready(function() {


    d3.json('/data/not_realtime/currency_rates_month/', function(error, data) {
        var boxDimensions = {width: 300, height: 15}

        // cleaning data
        // Avoid iterating through all stuff in prototype chain
        $.each(data, function(country_name, country_series){

            data_list = []
            for (i in _.range(30)){

                data_list.push(country_series[i]);
            }
            data[country_name] = data_list.reverse()
            // and flip it so the end sparkline will have oldest dates on the left
            // newest dates on the right
        });

    });
});

Next, let's start adding our visual elements at the point in our code represented by line 23 in the last code snippet. We can add our container divs for our set of sparklines. This includes a general container div, a text element, an svg element, and a circle element:



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        // Add container divs:

        d3.select('body')
          .selectAll('div')
          .data(d3.keys(data))
            .enter()
            .append('div')
            .classed('sparkline-container', function(d) {
                console.log(d);
                return true;
            })
            .style('top', function(d, i) { return i * 35 + 'px';})
            .style('left', '500px')

          .append('text')
            // get last 3 characters since format is like rates.JPY
            .text(function(d) {return d.slice(-3);})
          .append('svg')
            .attr('id', function(d) { return 'graph' + d.slice(-3);})
          .append('circle')
            .attr('id', function(d) { return 'circle' + d.slice(-3);})
            .attr('r', 1.5)
            .classed('sparkcircle', true);


Let's break down this code snippet. This code snippet basically creates the skeleton for our visualization -- it creates the text and a set of elements that will then be 'filled in' by the `drawSparkline` function in the following code snippet. In line 3, we select the body element, to which we will be appending our sparkline elements. We then add a set of divs in lines 4-13, with one div for each sparkline, since we have bound it to our dataset. Each div has a `sparkline-container` class, is offset from the left, and is offset from the top by a variable amount so that the sparklines are not all on top of each other.

In lines 15-17, we append the 3-character labels that represent the sparkline countries' names. Then, in lines 18-28, we add elements that will correspond with our little red circles, and the svg containers for our sparklines. .

Putting it all together:

Now that we have our visualization skeleton, we can use the `drawSparkline` function to actually append our sparklines:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
         function drawSparkline(svgElemId, circleElemId, data) {

            // create an SVG element inside the #elemId div that fills 100% of the div
        var graph = d3.select(svgElemId)
            .append("svg:svg")
            .attr("width", "100%")
            .attr("height", "100%");


        // X scale will fit values from 0-60 within pixels 0-width
        var x = d3.scale.linear()
            .domain([0, 60])
            .range([0, boxDimensions.width]);
        // Y scale will fit values from [the extent of our currency fluctations
        // dataset] within pixels 0-100
        var y = d3.scale.linear()
            .domain(d3.extent(data, function(d) { return d; }))
            .range([0, boxDimensions.height]);

        // create a line object that represents the SVN line we're creating
        var line = d3.svg.line()
            // Makes our plots smooth
            .interpolate('monotone')
            // assign the X function to plot our line as we wish
            .x(function(d,i) {
                return x(i);
            })
            .y(function(d) {
                return y(d);
            });

            // display the line by appending an svg:path element with the data line we created above
            graph.append("svg:path").attr("d", line(data));

        var circle = d3.select(circleElemId)
            .attr('cx', function(d, i) {return x(29); })
            .attr('cy', function(d) {console.log('circle');
                /* And this is really helpful for figuring out what is going on */
                console.log(svgElemId);
                console.log(data);
                console.log(data.slice(-1));
                return y(data.slice(-1)); })

        }

Lines 11 to 23 cover the same process that we would follow to add a line graph, except that we are making each graph tiny since it is a sparkline. Then in lines 35 to 42, we give our circle elements `cx` and `cy` attributes -- these tell us where the center of each circle will be. Without these attributes, our circles would not show up.

Finally, we call this function in order to draw our set of sparklines:


1
2
3
4
5
6
7
8
          // Then add sparklines and dots for circles
        $.each(data, function(country_name, country_series){

            drawSparkline('#graph' + country_name.slice(-3), '#circle' + country_name.slice(-3),data[country_name]);
        });
        // And move the SVGs by a bit so they are not on top of the
        // text boxes
        $('svg').css({left: 45, position: 'absolute'});

Check out the live example, or the github repository with the complete codebase.

6 comments:

Sign up to receive data viz talk tips and updates via email. We're low traffic and take privacy very seriously.