Skip to content

Instantly share code, notes, and snippets.

@davo
Forked from nsonnad/.block
Last active December 15, 2015 15:39
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save davo/5283263 to your computer and use it in GitHub Desktop.
Object constancy with multiple sets of time-series (update)

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.

[{"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}]
<!DOCTYPE html>
<html>
<head>
<title>Mist vs Brics</title>
<script type="text/javascript" src="http://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="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<script type="text/javascript" src="http://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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment