|
var margin = {top: 20, right: 20, bottom: 150, left: 60}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
// Create the svg holder. |
|
var svg = d3.select("#chart") |
|
.append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
// Generate a unique colour for every province. |
|
var colour = d3.scale.category10(); |
|
|
|
// Create colours and descriptions for the gender parity indicators |
|
var genderParityIndicators = [ |
|
{colour : "#fa4bd7", description :"More females than males enrolled"}, |
|
{colour : "#4b97fa", description :"More males than females enrolled"}, |
|
{colour : "#face4b", description :"Equal amount of males and females"} |
|
]; |
|
|
|
// Create a label for the current year. |
|
var yearLabel = svg.append("text") |
|
.attr("class", "year-label") |
|
.attr("x", 6) |
|
.attr("y", 50) |
|
.attr("fill", "rgb(127, 127, 127)") |
|
.attr("opacity", 0.4) |
|
.attr("font-size", "70px"); |
|
|
|
// Create a label for the province being hovered on. |
|
var provinceLabel = svg.append("text") |
|
.attr("class", "label") |
|
.attr("x", 6) |
|
.attr("y", height - 15) |
|
.attr("fill", "rgb(127, 127, 127)") |
|
.attr("opacity", 0.5) |
|
.attr("font-size", "50px"); |
|
|
|
// Global variable to hold the province data. |
|
var provinceData = []; |
|
|
|
d3.csv("data.csv", function(error, data) { |
|
// Convert the data elements to numbers. |
|
data.forEach(function(d) { |
|
d.year = +d.year; |
|
d.ler = +d.ler; |
|
d.ger = +d.ger; |
|
d.gpi = +d.gpi; |
|
d.matricpass = +d.matricpass; |
|
}); |
|
|
|
// Create the x-scale using the minimum and maximum learner-educator ratios. |
|
var xScale = d3.scale.linear() |
|
.domain(d3.extent(data, function (d) { return d.ler; } )).nice() |
|
.range([0, width]); |
|
|
|
// Create the x-axis. |
|
var xAxis = d3.svg.axis() |
|
.scale(xScale) |
|
.orient("bottom"); |
|
|
|
// Add the x-axis to the svg. |
|
svg.append('g') |
|
.attr("class", "axis") |
|
.attr("transform", "translate(0, " + height + ")") |
|
.call(xAxis) |
|
.append("text") |
|
.attr("class", "axis-label") |
|
.attr("x", width/2) |
|
.attr("y", 50) |
|
.style("text-anchor", "middle") |
|
.attr("font-size", "20px") |
|
.text("Learner-Educator Ratio"); |
|
|
|
// Create the x-scale using the minimum and maximum matric pass rates. |
|
var yScale = d3.scale.linear() |
|
.domain(d3.extent(data, function (d) { return d.matricpass; } )).nice() |
|
.range([height, 0]); |
|
|
|
// Create the y-axis. |
|
var yAxis = d3.svg.axis() |
|
.scale(yScale) |
|
.orient("left"); |
|
|
|
// Add the y-axis to the svg. |
|
svg.append('g') |
|
.attr("class", "axis") |
|
.call(yAxis) |
|
.append("text") |
|
.attr("class", "axis-label") |
|
.attr("transform", "rotate(-90)") |
|
.attr("y", -45) |
|
.attr("x", -height/2) |
|
.attr("dy", ".71em") |
|
.attr("font-size", "20px") |
|
.style("text-anchor", "middle") |
|
.text("Matric Pass Rate %"); |
|
|
|
// Nest the data in every province's name. |
|
provinceData = d3.nest() |
|
.key(function(d) { return d.province; }) |
|
.map(data); |
|
|
|
// Get an array of the provinces' names. |
|
var provinceNames = Object.keys(provinceData); |
|
|
|
// Determine for which years there is data and determine the first year. |
|
var years = []; |
|
var firstYear = 3000; |
|
data.forEach( function (d) { |
|
if (years.lastIndexOf(d.year) < 0) { |
|
if (d.year < firstYear) { |
|
firstYear = d.year; |
|
} |
|
years.push(Number(d.year)); |
|
} |
|
}); |
|
|
|
// Set the year label to the first year. |
|
yearLabel.text(firstYear); |
|
|
|
// Create a g element for every province. |
|
var province = svg.selectAll(".province") |
|
.data(provinceNames) |
|
.enter().append("g") |
|
.attr("class", "province"); |
|
|
|
// Create a circle for every province using the first year's data. |
|
province.selectAll("circle") |
|
.data( function(province) { |
|
// Filter every province's data to get their first year's data. |
|
return provinceData[province].filter( function (d) { |
|
if (d.year == firstYear) return true; |
|
return false; |
|
}); |
|
}) |
|
.enter().append("circle") |
|
.attr("class", "province-dot") |
|
.attr("cx", function(d, i) { return xScale(d.ler); }) |
|
.attr("cy", function(d, i) { return yScale(d.matricpass); }) |
|
.attr("r", function(d, i) { return Math.abs(d.learners/3000000 * 35); }) |
|
.style("fill", function(d, i) { |
|
return colour(provinceNames.indexOf(d.province)); |
|
}) |
|
.style("opacity", 0.7) |
|
.style("stroke", function (d) { |
|
if (d.gpi > 1) { |
|
return genderParityIndicators[0].colour; |
|
} else if (d.gpi < 1) { |
|
return genderParityIndicators[1].colour; |
|
} else { |
|
return genderParityIndicators[2].colour; |
|
} |
|
}) |
|
.style("stroke-width", "2.5px") |
|
.on("mouseover", function(d, i) { |
|
provinceLabel.text(d.province); |
|
}) |
|
.on("mouseout", function(d, i) { |
|
provinceLabel.text(""); |
|
}); |
|
|
|
// Create the slider for the years in the dataset. |
|
createSlider(years, firstYear); |
|
|
|
// Function to update the values of the graph to those of the currently selected |
|
// year. |
|
function update(year) { |
|
// Change the year label and add a transition as in the example on |
|
// http://jsfiddle.net/c5YVX/8/. |
|
yearLabel.transition().duration(700) |
|
.tween("text", function() { |
|
var i = d3.interpolate(this.textContent, year), |
|
prec = (year + "").split("."), |
|
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1; |
|
|
|
return function(t) { |
|
this.textContent = Math.round(i(t) * round) / round; |
|
}; |
|
}); |
|
|
|
// Update the circles of every province to the current year's data. |
|
svg.selectAll(".province").selectAll("circle") |
|
.data( function(province) { |
|
return provinceData[province].filter( function (d) { |
|
if (d.year == year) return true; |
|
return false; |
|
}); |
|
}) |
|
.transition() |
|
.duration(700) |
|
.attr("cx", function(d, i) { return xScale(d.ler); }) |
|
.attr("cy", function(d, i) { return yScale(d.matricpass); }) |
|
.attr("r", function(d, i) { return Math.abs(d.learners/3000000 * 35); |
|
}) |
|
.style("stroke", function (d) { |
|
if (d.gpi > 1) { |
|
return genderParityIndicators[0].colour; |
|
} else if (d.gpi < 1) { |
|
return genderParityIndicators[1].colour; |
|
} else { |
|
return genderParityIndicators[2].colour; |
|
} |
|
}); |
|
} |
|
|
|
// Function to create a slider for the year of which to display a graph. |
|
function createSlider(years, firstYear) { |
|
|
|
var sliderWidth = 400, |
|
sliderHeight = 23, |
|
numYears = years.length, |
|
sliderBarWidth = sliderWidth/numYears; |
|
|
|
var slider = svg.append("g") |
|
.attr("transform", "translate (" + (width/2 - sliderWidth/2 - 10) + "," + |
|
(height + 90) + ")"); |
|
|
|
// Current year for which to display the graph. |
|
var curYear = firstYear; |
|
|
|
// Create a title for the slider. |
|
slider.append("text") |
|
.text("Year") |
|
.attr("x", sliderWidth/2) |
|
.style("font-size", "18px") |
|
.style("text-anchor", "middle") |
|
; |
|
|
|
// Slider holder which can be clicked to change the year. |
|
slider.append("rect") |
|
.attr("x", 0) |
|
.attr("y", 10) |
|
.attr("width", sliderWidth) |
|
.attr("height", sliderHeight) |
|
.attr("fill", "white") |
|
.attr("stroke", "#000") |
|
.on("click", function() { |
|
var mouseX = d3.mouse(this)[0]; |
|
|
|
var yearIndex = Math.floor(mouseX/sliderWidth * numYears); |
|
// Move the slider bar to the area in which was clicked. |
|
d3.select("#sliderBar").transition().duration(100).attr("x", yearIndex * sliderBarWidth); |
|
curYear = firstYear + yearIndex; |
|
// Call the update function to update the data shown in the graph to |
|
// the newly selected year. |
|
update(curYear); |
|
}) |
|
; |
|
|
|
// Define the behaviour of the slider bar when dragged. |
|
var dragSlider = d3.behavior.drag() |
|
.on("drag", function() { |
|
var mouseX = d3.mouse(this)[0]; |
|
var sliderX = Number(slider.select("#sliderBar").attr("x")); |
|
// Move the slider bar to the area to which it was dragged (if |
|
// inside the slider) and call the update function to update the |
|
// graph's data. |
|
if (mouseX > sliderX + sliderBarWidth && sliderX < (numYears - 1) * |
|
sliderBarWidth) { |
|
d3.select("#sliderBar").attr("x", sliderX + sliderBarWidth); |
|
update(++curYear); |
|
} else if (mouseX < sliderX && sliderX > 0) { |
|
d3.select("#sliderBar").attr("x", sliderX - sliderBarWidth); |
|
update(--curYear); |
|
} |
|
}); |
|
|
|
// Create the slider bar which can be dragged to change the year. |
|
slider.append("rect") |
|
.attr("id", "sliderBar") |
|
.attr("x", 0) |
|
.attr("y", 10) |
|
.attr("width", sliderBarWidth) |
|
.attr("height", sliderHeight) |
|
.attr("fill", "rgb(127, 127, 127)") |
|
.style("cursor", "hand") |
|
.call(dragSlider) |
|
; |
|
|
|
// Create a scale for the slider's axis. |
|
var sliderScale = d3.scale.linear() |
|
.domain(d3.extent(years, function (d) { return d; } )).nice() |
|
.range([0, (numYears - 1)*sliderWidth/numYears]); |
|
|
|
// Set the axis ticks of the slider to the years for which there are data. |
|
var sliderTicks = years; |
|
|
|
// Create an axis for the slider using the years as ticks. |
|
var sliderAxis = d3.svg.axis() |
|
.scale(sliderScale) |
|
.tickValues(sliderTicks) |
|
.tickFormat(d3.format("d")) |
|
.orient("bottom"); |
|
|
|
// Add the slider axis to the slider. |
|
slider.append('g') |
|
.attr("class", "axis") |
|
.attr("transform", "translate(" + (sliderWidth/numYears)/2 + ", " + (sliderHeight + 10) + ")") |
|
.call(sliderAxis) |
|
; |
|
} |
|
|
|
}); |
|
|
|
// Create a legend for the gender parity indicators. |
|
var genderLegend = svg.selectAll(".legend") |
|
.data(genderParityIndicators) |
|
.enter().append("g") |
|
.attr("class", "legend") |
|
.attr("transform", function(d, i) { return "translate(" + (width - 210) + |
|
"," + (height + 70 + |
|
(i * 20)) + ")";}); |
|
|
|
genderLegend.append("rect") |
|
.attr("x", 0) |
|
.attr("width", 18) |
|
.attr("height", 18) |
|
.style("fill", function(d) { return d.colour ;} ); |
|
|
|
genderLegend.append("text") |
|
.attr("x", 23) |
|
.attr("y", 9) |
|
.attr("dy", ".35em") |
|
.text(function(d) { return d.description; }); |