|
/* |
|
* First attempt wit D3: |
|
* This script loads a CSV file with OSGeo Charter Members |
|
* information and presents it in several ways. |
|
* |
|
* Author: Jorge Sanz |
|
* Date: 14/07/27 |
|
*/ |
|
|
|
|
|
/* almost stupid and optimistic way to normalize strings for comparison */ |
|
var norm = function(string){ |
|
return ( string + "" ).toLowerCase().trim(); |
|
} |
|
|
|
/* |
|
* Function to generate the bar chart for |
|
* charters by year |
|
*/ |
|
var processDataByYear = function(csv_data){ |
|
/* Graph for the year */ |
|
var w = 1024; |
|
var h = 250; |
|
var barPadding = 2; |
|
var padding = 20; |
|
var xoffset = 30; |
|
|
|
|
|
/* group the data by year */ |
|
var dataByYear = d3.nest() |
|
.key(function(d) { return d.year;}) |
|
.sortKeys(d3.ascending) |
|
.entries(csv_data); |
|
|
|
var svg = d3.select('#byYear .graph') |
|
.append("svg") |
|
.attr("width", w) |
|
.attr("height", h) |
|
.attr("class","graph"); |
|
|
|
var yScale = d3.scale.linear() |
|
.domain([0, d3.max(dataByYear, function(d) { return d.values.length; })]) |
|
.rangeRound([h - padding, 0]); |
|
|
|
var xScale = d3.scale.ordinal() |
|
.domain(d3.range(dataByYear.length)) |
|
.rangeRoundBands([0, w - xoffset], 0.05); |
|
|
|
svg.selectAll("rect") |
|
.data(dataByYear) |
|
.enter() |
|
.append("rect") |
|
.attr("x", function(d, i) { |
|
return xScale(i) + xoffset; |
|
}) |
|
.attr("y", function(d) { |
|
return yScale(d.values.length); |
|
}) |
|
.attr("width", xScale.rangeBand()) |
|
.attr("height", function(d) { |
|
return h - yScale(d.values.length) - padding; |
|
}) |
|
.attr("class","bar"); |
|
|
|
|
|
svg.append("g") |
|
.selectAll("text") |
|
.data(dataByYear) |
|
.enter() |
|
.append("text") |
|
.text(function(d) { |
|
return d.values.length; |
|
}) |
|
.attr("text-anchor", "middle") |
|
.attr("x", function(d, i) { |
|
return xScale(i) + xoffset + xScale.rangeBand()/2; |
|
}) |
|
.attr("y", function(d) { |
|
return yScale(d.values.length) + 14; |
|
}) |
|
.attr("class", "label"); |
|
|
|
|
|
svg.append("g") |
|
.selectAll("text") |
|
.data(dataByYear) |
|
.enter() |
|
.append("text") |
|
.text(function(d) { |
|
return d.key; |
|
}) |
|
.attr("text-anchor", "middle") |
|
.attr("x", function(d, i) { |
|
prop = ( w - xoffset) / dataByYear.length; |
|
return i * prop + (prop - barPadding) / 2 + xoffset; |
|
}) |
|
.attr("y", function(d) { |
|
return h; |
|
}) |
|
.attr("class", "label xaxe"); |
|
|
|
//Define X axis |
|
var yAxis = d3.svg.axis() |
|
.scale(yScale) |
|
.orient("left") |
|
.ticks(5); |
|
|
|
//Create X axis |
|
svg.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(" + xoffset + ",0)") |
|
.call(yAxis); |
|
}; |
|
|
|
|
|
/* |
|
* Function to process the countries geojson |
|
* join it with charters data and |
|
* produce the pie chart and the map |
|
*/ |
|
var processDataByCountry = function(csv_data, countries){ |
|
|
|
/* group the data by country */ |
|
var w = 700, |
|
h = 350, |
|
colors = ["#ccc","#41ab5d","#238b45","#005a32"], |
|
buckets = colors.length; |
|
|
|
var quantize = d3.scale.quantile() |
|
.domain([0,1,5,15,100]) |
|
.range(d3.range(buckets)); |
|
|
|
var dataByCountry = d3.nest() |
|
.key(function(d) { return d.country;}) |
|
.rollup(function(d) { |
|
return d3.sum(d, function(g) {return 1; }); |
|
}) |
|
.sortKeys(d3.ascending) |
|
.entries(csv_data); |
|
|
|
d3.select('#chartersByCountry') |
|
.selectAll('span') |
|
.data(dataByCountry) |
|
.enter() |
|
.append('span') |
|
.attr("id",function(d,i){ |
|
return "charter-country-text-" + i; |
|
}) |
|
.text(function(country,i){ |
|
result = country.key + " (" + country.values + ")"; |
|
return i < dataByCountry.length - 1 ? result + " · " : result; |
|
}) |
|
.style("color", function(d) {return colors[quantize(d.values)]}); |
|
|
|
// data by Country |
|
|
|
//Define map projection |
|
var projection = d3.geo.robinson() |
|
.scale(110) |
|
.translate([w / 2, h / 2]) |
|
.precision(.1); |
|
|
|
//Define path generator |
|
var path = d3.geo.path() |
|
.projection(projection); |
|
|
|
var graticule = d3.geo.graticule(); |
|
|
|
// CREATE THE SVG |
|
|
|
var svgMap = d3.select('#byCountry .graph') |
|
.append("svg") |
|
.attr("class","map") |
|
.attr("width", w) |
|
.attr("height", h); |
|
|
|
svgMap.append("path") |
|
.datum(graticule) |
|
.attr("class", "graticule") |
|
.attr("d", path); |
|
|
|
|
|
//Merge the ag. data and GeoJSON |
|
var dataByCountryPushed = []; |
|
var features_with_charters = countries.features.map(function(country){ |
|
var result = { |
|
'type' : 'Feature', |
|
'geometry' : country.geometry, |
|
'properties' : { |
|
'admin' : country.properties.admin, |
|
'region' : country.properties.region_wb, |
|
'charters' : 0 |
|
} |
|
}; |
|
|
|
//find data into the dataByCountry |
|
for (i in dataByCountry){ |
|
if (norm(dataByCountry[i].key) == norm(country.properties.admin)){ |
|
result.properties['charters'] = dataByCountry[i].values; |
|
dataByCountryPushed.push(dataByCountry[i]); |
|
break; |
|
} |
|
} |
|
return result; |
|
}); |
|
var countriesCharters = {'type':'FeatureCollection','features':features_with_charters}; |
|
|
|
if (dataByCountry.length != dataByCountryPushed.length){ |
|
dataByCountry.forEach(function(d){ |
|
console.assert(dataByCountryPushed.indexOf(d)!=-1,d) |
|
}) |
|
} |
|
|
|
svgMap.selectAll("path") |
|
.data(countriesCharters.features) |
|
.enter() |
|
.append("path") |
|
.attr("d", path) |
|
.style("fill", function(d) { |
|
color = colors[quantize(d.properties.charters)] |
|
return color; |
|
}) |
|
.on("mouseover",function(d){ |
|
var r = -1; |
|
for (i in dataByCountry){ |
|
if (norm(d.properties.admin) === norm(dataByCountry[i].key)){ |
|
r = i; |
|
break; |
|
} |
|
} |
|
if (r != -1) |
|
d3.select("#charter-country-text-"+i) |
|
.classed("highlighted",true); |
|
|
|
}) |
|
.on("mouseout",function(d){ |
|
var r = -1; |
|
for (i in dataByCountry){ |
|
if (norm(d.properties.admin) === norm(dataByCountry[i].key)){ |
|
r = i; |
|
break; |
|
} |
|
} |
|
if (r != -1) |
|
d3.select("#charter-country-text-"+i) |
|
.classed("highlighted",false); |
|
});; |
|
|
|
|
|
// Regroup by regiov_wb |
|
|
|
/* normalize countries and regions */ |
|
var dataByRegion = d3.nest() |
|
.key(function(d) { |
|
region = d.properties.region |
|
return region; |
|
}) |
|
.rollup(function(d) { |
|
return { |
|
'totalCharters' : d3.sum(d, function(g) {return g.properties.charters; }), |
|
'countries' : d.map(function(country){ |
|
return { |
|
'country' : country.properties.admin, |
|
'charters': country.properties.charters |
|
} |
|
}) |
|
} |
|
}) |
|
.sortKeys(d3.ascending) |
|
.entries(countriesCharters.features.filter(function(d){return d.properties.charters>0;})); |
|
|
|
var color = d3.scale.category10(); |
|
|
|
// Populate the list |
|
d3.select('#chartersByRegion') |
|
.selectAll('span') |
|
.data(dataByRegion,function(d){return d.key}) |
|
.enter() |
|
.append('span') |
|
.attr("style", function(d, i) { |
|
return "color:" + color(i); |
|
}) |
|
.attr("id",function(d,i){ |
|
return "charter-region-text-" + i; |
|
}) |
|
.text(function(d,i){ |
|
result = d.key + " (" + d.values.totalCharters + ")"; |
|
return i < dataByRegion.length - 1 ? result + " · " : result; |
|
}); |
|
|
|
// Graph for the year |
|
var pie = d3.layout.pie().value(function(d){return d.values.totalCharters}); |
|
var w = 200; |
|
var h = 200; |
|
|
|
var outerRadius = w / 2; |
|
var innerRadius = 0; |
|
var arc = d3.svg.arc() |
|
.innerRadius(innerRadius) |
|
.outerRadius(outerRadius); |
|
|
|
var svgPie = d3.select('#byRegion .graph') |
|
.append("svg") |
|
.attr("width", w) |
|
.attr("height", h) |
|
.attr("class","graph"); |
|
|
|
var arcs = svgPie.append("g") |
|
.attr("class","pieArcs") |
|
.selectAll("g.arc") |
|
.data(pie(dataByRegion)) |
|
.enter() |
|
.append("g") |
|
.attr("class", "arc") |
|
.attr("transform", "translate(" + outerRadius + ", " + outerRadius + ")") |
|
.append("path") |
|
.attr("fill", function(d, i) { |
|
return color(i); |
|
}) |
|
.attr("d", arc) |
|
.on("mouseover",function(d,i){ |
|
d3.select('.graphDetail') |
|
.append('h3') |
|
.text(d.data.key); |
|
|
|
d3.select('.graphDetail') |
|
.append('p') |
|
.attr("class","inline-list") |
|
.selectAll('span') |
|
.data(d.data.values.countries) |
|
.enter() |
|
.append('span') |
|
.text(function(l,i){ |
|
result = l.country + " (" + l.charters + ")"; |
|
return i < d.data.values.countries.length - 1 ? result + " · " : result; |
|
}); |
|
d3.select("#charter-region-text-"+i) |
|
.classed("highlighted",true); |
|
|
|
}) |
|
.on("mouseout",function(d,i){ |
|
d3.select('.graphDetail h3').remove(); |
|
d3.select('.graphDetail p').remove(); |
|
d3.select("#charter-region-text-"+i) |
|
.classed("highlighted",false); |
|
}); |
|
}; |
|
|
|
|
|
|
|
/* |
|
* Where the action begins |
|
*/ |
|
d3.csv("charters.csv", function(csv_data){ |
|
processDataByYear(csv_data); |
|
|
|
d3.json("countries.json", function(error, countries) { |
|
processDataByCountry(csv_data,countries); |
|
}); |
|
}); |