block by davo 5283263

Object constancy with multiple sets of time-series (update)

Full Screen

Forked from the work of Nikhil Sonnad, called Object constancy with multiple sets of time-series

Add support for JSON and data binding with Knockout.js (it needs improvement).

Original README.md

This chart compares the BRIC countries (Brazil, Russia, India and China) with a new group of upstarts, MIST (Mexico, Indonesia, South Korea, Turkey). The data are from the World Bank. It was my attempt to achieve object constancy for multiple sets of time series data. I used d3.nest() to sort one big CSV file based on each economic indicator, then used d3.key() on the header row (the countries) to make ensure constancy. Check the boxes at the top to highlight the country groups. An interpretation of this visual can be found on my website.

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Mist vs Brics</title>
    <script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
<style type="text/css">

body {
  font: 12px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.x.axis path {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.line {
  fill: none;
  stroke-width: 7px;
}

.country {opacity: 0.3;}
.country:hover {opacity:1;}

</style>
</head>
<body>
  <div>
      <input type="checkbox" id="bric" onclick="briclight()">BRICs</input>
      <input type="checkbox" id="mist" onclick="mistlight()">MIST</input>
  </div>


<span id="menu">
   <select class="selector" data-bind="options: indicator,
                               optionsText: 'name', optionsValue: 'indicatorCode',
                               value: choosenIndicator"></select>
</span>

<script type="text/javascript" src="//knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript">



// set the stage for the visualization
    var margin = {top: 20, right: 80, bottom: 30, left: 50},
        w = 660 - margin.left - margin.right,
        h = 400 - margin.top - margin.bottom,
        x = d3.time.scale().range([0, w]),
        y = d3.scale.linear().range([h, 0]);
        parseDate = d3.time.format("%Y").parse;
        
    var color = d3.scale.category10(); // to generate a different color for each line
    
    // to be used later
    var countries,
        filtered,
        transpose;
    
    // where the line gets its properties, how it will be interpolated
    var line = d3.svg.line()
       .interpolate("basis")
       .x(function(d) { return x(d.year); })
       .y(function(d) { return y(d.stat); });
    
    // add svg box where viz will go    
    var svg = d3.select("body").append("svg")
        .attr("width", w + margin.left + margin.right)
        .attr("height", h + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // define the x axis and its class, append it to svg 
    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
       svg.append("svg:g")
        .attr("class", "x axis");

    // define the y axis and its class, append it to svg
    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        svg.append("svg:g")
        .attr("class", "y axis");
    
    // force data to update when menu is changed    
    var menu = d3.select("#menu select")
    .on("change", change);    
          
// put data from json into countries variable
//run redraw function that will refresh whenever a new data series is selected 
d3.json("brics-mist.json", function(json) {
    countries = json;
        redraw();
    });

d3.select(window)
    .on("keydown", function() { altKey = d3.event.altKey; })
    .on("keyup", function() { altKey = false; });

var altKey;

// set terms of transition that will take place
// when a new economic indicator is chosen   
function change() {
  clearTimeout(timeout);

  d3.transition()
      .duration(altKey ? 7500 : 1500)
      .each(redraw);
}


function indicatorsViewModel() {

    this.indicator = [
        { indicatorCode: 'GDPPC', name: 'GDP per capita (current US$)'},
        { indicatorCode: 'URBPOP', name: 'Urban population (% of total)'}
    ];

    this.choosenIndicator = ko.observable(); 

}


ko.applyBindings(new indicatorsViewModel());

// all the meat goes in the redraw function
function redraw() {
    
    // create data nests based on economic indicator (series)
    var nested = d3.nest()
    .key(function(d) { return d.indicatorCode; })


    .map(countries)
    
    // get value from menu selection
    // the option values are set in HTML and correspond
    //to the [indicatorCode] value we used to nest the data  
    var series = menu.property("value");
    
    // only retrieve data from the selected series, using the nest we just created
    var data = nested[series];
    
    // for object constancy we will need to set "keys", one for each country.
    // the keyring variable contains only the names of the countries
    var keyring = d3.keys(data[0]).filter(function(key) { 
          return (key !== "indicatorName" && key !== "yearCode" && key !== "indicatorCode" && key !== "year");
      });
    
    // get the year and related statistics, map them to each country separately 
    var transpose = keyring.map(function(name) {
            return {
              name: name,
              values: data.map(function(d) {
                return {year: parseDate(d.year.toString()), stat: +d[name]};
              })
            };
        });

    // set the x and y domains as the max and min
    // of the related year and statistics, respectively
    x.domain([
    d3.min(transpose, function(c) { return d3.min(c.values, function(v) { return v.year; }); }),
    d3.max(transpose, function(c) { return d3.max(c.values, function(v) { return v.year; }); })
  ]);

    y.domain([
    d3.min(transpose, function(c) { return d3.min(c.values, function(v) { return v.stat; }); }),
    d3.max(transpose, function(c) { return d3.max(c.values, function(v) { return v.stat; }); })
  ]);

    // announce to d3 that we will be using something called
    // "country" that makes use of the transposed data 
    var country = svg.selectAll(".country")
      .data(transpose);
     
    // create separate groups for each country
    // assign them a class and individual IDs (for styling) 
    var countryEnter = country.enter().append("g")
      .attr("class", "country")
      .attr("id", function(d) { return d.name; });
    
    // draw the lines and color them according to their names
    countryEnter.append("path")
      .attr("class", "line")
      .attr("d", function(d) { return line(d.values); })
      .style("stroke", function(d) { return color(d.name); });

    // create lables for each country
    // set their position to that of the last year and stat
    countryEnter.append("text")
     .attr("class", "names")
     .datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
     .attr("transform", function(d) { return "translate(" + x(d.value.year) + "," + y(d.value.stat) + ")"; })
     .attr("x", 4)
     .attr("dy", ".35em")
     .text(function(d) { return d.name; });

    // set variable for updating visualization
    var countryUpdate = d3.transition(country);
    
    // change values of path to those of the new series
    countryUpdate.select("path")
      .attr("d", function(d) { return line(d.values); });
    
    // change position of text alongside the moving path  
    countryUpdate.select("text")
       .attr("transform", function(d) { return "translate(" + x(d.values[d.values.length - 1].year) + "," + y(d.values[d.values.length - 1].stat) + ")"; });
  
  // update the axes, though only the y axis will change    
      d3.transition(svg).select(".y.axis")
          .call(yAxis);   
          
      d3.transition(svg).select(".x.axis")
            .attr("transform", "translate(0," + h + ")")
          .call(xAxis);
          
// that concludes redraw()
}

// automatically change value after a few seconds
var timeout = setTimeout(function() {
  menu.property("value", "ENEUSE").node().focus();
  change();
}, 7000);

// ugly javascript for highlighting the two groups of countries
function briclight() {
    var chkbox = document.getElementById("bric");
    if (chkbox.checked) {
        document.getElementById("China").style.cssText = "opacity:1;",
        document.getElementById("Brazil").style.cssText = "opacity:1;",
        document.getElementById("India").style.cssText = "opacity:1;",
        document.getElementById("Russia").style.cssText = "opacity:1;"
    } else { 
        document.getElementById("China").style.cssText = "",
        document.getElementById("Brazil").style.cssText = "",
        document.getElementById("India").style.cssText = "",
        document.getElementById("Russia").style.cssText = "";
    }};
    
function mistlight() {
    var chkbox = document.getElementById("mist")
    if (chkbox.checked) {
        document.getElementById("Mexico").style.cssText = "opacity:1;",
        document.getElementById("Indonesia").style.cssText = "opacity:1;",
        document.getElementById("S Korea").style.cssText = "opacity:1;",
        document.getElementById("Turkey").style.cssText = "opacity:1;"
    } else { 
        document.getElementById("Mexico").style.cssText = "",
        document.getElementById("Indonesia").style.cssText = "",
        document.getElementById("S Korea").style.cssText = "",
        document.getElementById("Turkey").style.cssText = "";
    }};

// done!
  </script>
  </body>
</html>

brics-mist.json

[{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1992,"Brazil":2526.596184,"Russia":3095.08716,"India":322.243454,"Indonesia":730.2214455,"China":362.808414,"Mexico":4154.426906,"S Korea":7555.272527,"Turkey":2840.368021},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1993,"Brazil":2791.968684,"Russia":2929.303282,"India":306.1686498,"Indonesia":816.464647,"China":373.8000229,"Mexico":4524.709377,"S Korea":8219.896199,"Turkey":3167.52691},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1994,"Brazil":3426.840162,"Russia":2663.456988,"India":351.8848828,"Indonesia":900.2674297,"China":469.2131942,"Mexico":4650.114233,"S Korea":9525.43563,"Turkey":2256.731126},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1995,"Brazil":4751.065263,"Russia":2669.946123,"India":380.0984013,"Indonesia":1013.699545,"China":604.2280606,"Mexico":3107.073918,"S Korea":11467.81385,"Turkey":2879.248308},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1996,"Brazil":5109.348611,"Russia":2651.442018,"India":406.8857207,"Indonesia":1124.16197,"China":703.1207994,"Mexico":3546.928961,"S Korea":12249.17315,"Turkey":3033.593361},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1997,"Brazil":5220.856542,"Russia":2748.917437,"India":422.9243667,"Indonesia":1052.107705,"China":774.467161,"Mexico":4206.564036,"S Korea":11234.777,"Turkey":3123.142513},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1998,"Brazil":4980.980645,"Russia":1844.485782,"India":420.9653204,"Indonesia":459.2276532,"China":820.8630768,"Mexico":4342.334118,"S Korea":7462.838645,"Turkey":4361.442138},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":1999,"Brazil":3413.259971,"Russia":1338.986444,"India":448.0969889,"Indonesia":664.7397402,"China":864.7303144,"Mexico":4884.625014,"S Korea":9554.439443,"Turkey":3983.746202},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2000,"Brazil":3696.146772,"Russia":1775.141291,"India":450.4151061,"Indonesia":773.3109699,"China":949.1780621,"Mexico":5816.614481,"S Korea":11346.66499,"Turkey":4189.478062},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2001,"Brazil":3129.755456,"Russia":2100.743786,"India":459.5766355,"Indonesia":742.1107816,"China":1041.637704,"Mexico":6139.301715,"S Korea":10654.93555,"Turkey":3036.72709},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2002,"Brazil":2812.334223,"Russia":2375.162934,"India":480.2069446,"Indonesia":893.3199025,"China":1135.44795,"Mexico":6324.167505,"S Korea":12093.7573,"Turkey":3553.066261},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2003,"Brazil":3041.677796,"Russia":2976.137049,"India":558.4416044,"Indonesia":1058.299984,"China":1273.640743,"Mexico":6740.20548,"S Korea":13451.22942,"Turkey":4567.499135},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2004,"Brazil":3609.875507,"Russia":4108.574488,"India":642.556503,"Indonesia":1143.496951,"China":1490.380056,"Mexico":7223.869614,"S Korea":15028.94015,"Turkey":5832.689345},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2005,"Brazil":4743.264112,"Russia":5337.065324,"India":731.7417369,"Indonesia":1257.653396,"China":1731.125235,"Mexico":7972.553641,"S Korea":17550.85389,"Turkey":7087.720249},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2006,"Brazil":5793.400957,"Russia":6946.880998,"India":820.2983339,"Indonesia":1585.650791,"China":2069.343631,"Mexico":8830.844748,"S Korea":19676.12418,"Turkey":7687.125651},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2007,"Brazil":7197.031306,"Russia":9146.41636,"India":1055.136489,"Indonesia":1859.302639,"China":2651.260121,"Mexico":9484.731556,"S Korea":21590.10558,"Turkey":9246.030405},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2008,"Brazil":8628.952841,"Russia":11700.22112,"India":1027.906574,"Indonesia":2171.7048,"China":3413.588661,"Mexico":9893.414594,"S Korea":19028.01293,"Turkey":10297.50544},
{"indicatorName":"GDP per capita (current US$)","indicatorCode":"GDPPC","year":2009,"Brazil":8391.668592,"Russia":8615.658757,"India":1126.945129,"Indonesia":2272.733849,"China":3748.934494,"Mexico":7875.820872,"S Korea":16958.65239,"Turkey":8553.741453},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1992,"Brazil":75.3972,"Russia":73.3852,"India":25.971,"Indonesia":32.572,"China":28.2496,"Mexico":72.1986,"S Korea":75.602,"Turkey":60.371},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1993,"Brazil":76.1348,"Russia":73.3808,"India":26.183,"Indonesia":33.566,"China":29.1534,"Mexico":72.5884,"S Korea":76.481,"Turkey":60.955},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1994,"Brazil":76.8724,"Russia":73.3764,"India":26.395,"Indonesia":34.56,"China":30.0572,"Mexico":72.9782,"S Korea":77.36,"Turkey":61.539},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1995,"Brazil":77.61,"Russia":73.372,"India":26.607,"Indonesia":35.554,"China":30.961,"Mexico":73.368,"S Korea":78.239,"Turkey":62.123},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1996,"Brazil":78.3264,"Russia":73.3676,"India":26.819,"Indonesia":36.8436,"China":31.9442,"Mexico":73.6388,"S Korea":78.5154,"Turkey":62.6466},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1997,"Brazil":79.0428,"Russia":73.3632,"India":27.031,"Indonesia":38.1332,"China":32.9274,"Mexico":73.9096,"S Korea":78.7918,"Turkey":63.1702},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1998,"Brazil":79.7592,"Russia":73.3588,"India":27.243,"Indonesia":39.4228,"China":33.9106,"Mexico":74.1804,"S Korea":79.0682,"Turkey":63.6938},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":1999,"Brazil":80.4756,"Russia":73.3544,"India":27.455,"Indonesia":40.7124,"China":34.8938,"Mexico":74.4512,"S Korea":79.3446,"Turkey":64.2174},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2000,"Brazil":81.192,"Russia":73.35,"India":27.667,"Indonesia":42.002,"China":35.877,"Mexico":74.722,"S Korea":79.621,"Turkey":64.741},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2001,"Brazil":81.5204,"Russia":73.266,"India":27.9806,"Indonesia":42.789,"China":37.206,"Mexico":75.0392,"S Korea":79.9658,"Turkey":65.161},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2002,"Brazil":81.8488,"Russia":73.182,"India":28.2942,"Indonesia":43.576,"China":38.535,"Mexico":75.3564,"S Korea":80.3106,"Turkey":65.581},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2003,"Brazil":82.1772,"Russia":73.098,"India":28.6078,"Indonesia":44.363,"China":39.864,"Mexico":75.6736,"S Korea":80.6554,"Turkey":66.001},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2004,"Brazil":82.5056,"Russia":73.014,"India":28.9214,"Indonesia":45.15,"China":41.193,"Mexico":75.9908,"S Korea":81.0002,"Turkey":66.421},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2005,"Brazil":82.834,"Russia":72.93,"India":29.235,"Indonesia":45.937,"China":42.522,"Mexico":76.308,"S Korea":81.345,"Turkey":66.841},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2006,"Brazil":83.1342,"Russia":73.0744,"India":29.574,"Indonesia":46.7344,"China":43.8628,"Mexico":76.6114,"S Korea":81.6626,"Turkey":67.5702},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2007,"Brazil":83.4344,"Russia":73.2188,"India":29.913,"Indonesia":47.5318,"China":45.2036,"Mexico":76.9148,"S Korea":81.9802,"Turkey":68.2994},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2008,"Brazil":83.7346,"Russia":73.3632,"India":30.252,"Indonesia":48.3292,"China":46.5444,"Mexico":77.2182,"S Korea":82.2978,"Turkey":69.0286},
{"indicatorName":"Urban population (% of total)","indicatorCode":"URBPOP","year":2009,"Brazil":84.0348,"Russia":73.5076,"India":30.591,"Indonesia":49.1266,"China":47.8852,"Mexico":77.5216,"S Korea":82.6154,"Turkey":69.7578}]