Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save delenamalan/f26f4ef299f830492589 to your computer and use it in GitHub Desktop.
Save delenamalan/f26f4ef299f830492589 to your computer and use it in GitHub Desktop.
Learner-educator ratios and matric pass rates in South Africa's provinces
Learner-educator ratios and matric pass rates in South Africa's provinces
province year ler ger gpi matricpass learners
Western Cape 2013 31.08 79 1.05 85.1 1005466
Gauteng 2013 32 83 1 87 1899542
Eastern Cape 2013 29.8 103 1 64.9 1881605
Limpopo 2013 30.38 110 0.97 71.8 1662106
Mpumalanga 2013 30.73 95 0.98 77.6 1025859
KwaZulu-Natal 2013 30.66 100 0.98 77.4 2798975
Free State 2013 27.39 96 0.99 87.4 649806
North West 2013 30.71 89 0.99 87.2 773040
Northern Cape 2013 32.03 94 1 74.5 279445
Western Cape 2012 31.08 79 1.05 82.8 1005466
Gauteng 2012 31.41 83 1 83.9 1858745
Eastern Cape 2012 29.12 103 1 61.6 1895989
Limpopo 2012 30.12 110 0.97 66.9 1665013
Mpumalanga 2012 31.09 95 0.98 70 1027851
KwaZulu-Natal 2012 31.17 100 0.98 73.1 2812844
Free State 2012 27.09 96 0.99 81.1 646093
North West 2012 30.56 89 0.99 79.5 760272
Northern Cape 2012 31.76 94 1 74.6 274189
Gauteng 2011 31.37 82 1.04 81.1 1814167
Eastern Cape 2011 29 95 1.01 58.1 1910265
Western Cape 2011 30.29 81 1.06 82.9 970319
Limpopo 2011 29.56 99 0.96 63.9 1645746
Mpumalanga 2011 30.84 99 0.98 64.8 1021722
KwaZulu-Natal 2011 31.36 91 0.98 68.1 2781830
Free State 2011 27.64 92 0.98 75.7 641219
North West 2011 30.09 89 0.97 77.8 751294
Northern Cape 2011 31.33 92 1.01 68.8 271474
Gauteng 2010 30.94 82 1.04 78.6 1777794
Eastern Cape 2010 30.07 99 1.02 58.3 2003129
Western Cape 2010 30.11 82 1.06 76.8 959714
Limpopo 2010 29.66 99 0.97 57.9 1660700
Mpumalanga 2010 30.49 97 0.98 56.8 1013760
KwaZulu-Natal 2010 31.37 90 0.99 70.7 2743979
Free State 2010 27.75 91 0.99 70.7 638756
North West 2010 29.76 89 0.98 75.7 746096
Northern Cape 2010 30.9 89 1.01 72.3 266296
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; });
<!DOCTYPE html>
<meta charset="utf-8">
<title>Education Graph</title>
<style>
body {
font: 12px sans-serif;
position: relative;
width: 960px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
shape-rendering: crispEdges;
}
</style>
<div id="chart">
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="graph.js"></script>
- Show label indicating that the size of the circles are representative of the number of learners in a province.
- Break up into separate functions.
- Add a regression line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment