Skip to content

Instantly share code, notes, and snippets.

@leondutoit
Last active May 28, 2019 02:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save leondutoit/2532501d4c916005a386 to your computer and use it in GitHub Desktop.
Save leondutoit/2532501d4c916005a386 to your computer and use it in GitHub Desktop.
Norwegian parliamentary voting patterns

Holder de Ord (Norwegian for "keep their word") does information work by using and analysing parliamentary voting data. At the start of a new electoral cycle, parties declare their policy intentions to their voters. Are they really doing what they said they would? How are voting patterns influenced when parties form coalitions? Which are the most divisive issues in parliament? These are all important questions that are typically difficult to anwer without spending hours on research.

This visualisation presents a sample of voting data showing to what degree different party combinations voted in the same way on a set of issues. Neither the combinations nor the issues are exhaustive. Rather, the visualisation is a proof of concept of how such a presentation can help answer the types of questions mentioned above. A simple mouseover provides an explanatory note to the investigator.

<!DOCTYPE html>
<html>
<head>
<title>Norwegian political party voting similarity</title>
<style type="text/css">
.title {
font-weight: bold;
font-family: sans-serif;
}
.x-axis .y-axis line {
fill: none;
stroke: none;
stroke-opacity:.2;
shape-rendering: crispEdges;
}
.x-axis path {
stroke: white;
fill: none;
}
.y-axis path {
stroke: white;
fill: none;
}
.x-axis text {
font-size: 9px;
}
.y-axis text {
font-size: 9px;
font-family: sans-serif;
}
.legend-y-axis path {
fill: none;
}
.legend-y-axis text {
font-size: 9px;
font-family: sans-serif;
}
.labels {
font-size: 9px;
}
.domain {
fill-opacity: none;
stroke-opacity: none;
}
#ten {
fill: #a50026;
}
#nine {
fill: #d73027;
}
#eight {
fill: #f46d43;
}
#seven {
fill: #fdae61;
}
#six {
fill: #fee090;
}
#five {
fill: #ffffbf;
}
#four {
fill: #e0f3f8;
}
#three {
fill: #abd9e9;
}
#two {
fill: #74add1;
}
#one {
fill: #4575b4;
}
#zero {
fill: #313695;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
</head>
<body>
<script type="text/javascript">
var createHolderDeOrdViz = (function() {
var assignColour = function(data) {
var roundedData = d3.round(data/10);
var colourId;
switch (roundedData) {
case 10:
colourId = "ten";
break;
case 9:
colourId = "nine";
break;
case 8:
colourId = "eight";
break;
case 7:
colourId = "seven";
break;
case 6:
colourId = "six";
break;
case 5:
colourId = "five";
break;
case 4:
colourId = "four";
break;
case 3:
colourId = "three";
break;
case 2:
colourId = "two";
break;
case 1:
colourId = "one";
break;
case 0:
colourId = "zero";
break;
default:
colourId = "";
}
return colourId;
}
var createLegendSpec = function(margin, widthInit, heightInit) {
var xInit = widthInit - margin.right,
yInit = margin.top;
return {
"xPos": xInit + margin.top,
"yPos": yInit + margin.top,
"width": margin.right - (margin.top*2),
"height": heightInit/2
}
}
var createLegendSvg = function(spec) {
var newPosition = "translate(" + spec.xPos + "," + spec.yPos + ")";
return svg
.append("g")
.attr("class", "legend-svg")
.attr("transform", newPosition);
}
var parseClassValues = function(classValue) {
var vals = classValue.split(",");
var parsed = [];
for (i = 0; i < vals.length; i++) {
parsed.push(parseInt(vals[i]));
}
return parsed;
}
var buildExplanation = function(classValue, data, issues, partyCombinations) {
var classInfo = parseClassValues(classValue);
var rowNum = classInfo[0];
var colNum = classInfo[1];
return "".concat(partyCombinations[colNum], " voted ", data[rowNum].values[colNum],
"% in agreement on ", issues[rowNum], ".");
}
var drawLegend = function(margin, widthInit, heightInit) {
var spec = createLegendSpec(margin, widthInit, heightInit)
var legendSvg = createLegendSvg(spec)
var legendData = {
"labels": [
"[95%, 100%]", "[85%, 95%)", "[75%, 85%)",
"[65%, 75%)", "[55%, 65%)", "[45%, 55%)",
"[35%, 45%)", "[25%, 35%)", "[15%, 25%)",
"[5%, 15%)", "[0%, 5%)"],
"mappingData": [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
}
var lyScale = d3.scale.ordinal()
.domain(legendData.labels.map(function(d) {return d; }))
.rangeRoundBands([0, spec.height], 0, 0);
var ly = legendSvg
.append("g")
.attr("class", "legend-y-axis")
.attr("transform", "translate(" + spec.width/6 + " ,0)");
var lyAxis = d3.svg.axis().scale(lyScale).orient("right");
ly.call(lyAxis);
var legend = legendSvg
.append("g")
.attr("class", "legend")
.selectAll("rect")
.data(legendData.mappingData)
.enter()
.append("rect")
.attr("x", "0")
.attr("y", function(d, i) {
return lyScale(legendData.labels[i]);
})
.attr("width", spec.width/6)
.attr("height", spec.height/legendData.mappingData.length)
.attr("id", function(d) { return assignColour(d); })
.attr("stroke", "black");
return legend;
}
var margin = {top: 70, bottom: 30, left: 200, right: 200},
widthInit = 1000;
heightInit = 450;
width = widthInit - margin.left - margin.right,
height = heightInit - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("class", "base-svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("viewBox", "0 0 " + widthInit + " " + heightInit)
.attr("preserveAspectRatio", "xMidYMid");
var xScale = d3.scale.ordinal().rangeRoundBands([0, width], 0, 0),
yScale = d3.scale.ordinal().rangeRoundBands([0, height], 0, 0);
var legend = drawLegend(margin, widthInit, heightInit);
var graphSvg = svg.append("g").attr("class", "graph-svg")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = graphSvg.append("g").attr("class", "x-axis"),
y = graphSvg.append("g").attr("class", "y-axis");
var dataUrl = "data.json";
d3.json("testdata.json", function(sourceData) {
var partyCombinations = sourceData[0].partyCombinations,
issues = sourceData[0].issues,
data = sourceData[0].data;
xScale.domain(partyCombinations.map(function(d) { return d; }));
yScale.domain(issues.map(function(d) {return d; }));
d3.select(".base-svg").append("text")
.attr("x", margin.left)
.attr("y", margin.top/2)
.attr("text-anchor", "start")
.attr("class", "title")
.text("Party agreement (% vote similarity per issue)");
var row = 0;
data.forEach(function(issue) {
var currentIssue = issue.values;
var blocks = graphSvg
.append("g")
.attr("class", function(d, i) { return issue.issue; })
.selectAll("rect")
.data(currentIssue)
.enter()
.append("rect")
.attr("x", function(d, i) { return xScale(partyCombinations[i]); })
.attr("y", function() { return yScale(issue.issue); })
.attr("id", function(d) { return assignColour(d); })
.attr("width", width/currentIssue.length)
.attr("height", height/issues.length)
.attr("stroke", "black")
.attr("class", function(d, i) { return row + "," + i; });
blocks
.on("mouseover", function() {
d3.select(this).attr("fill-opacity", "0.3");
var classVals = d3.select(this).attr("class");
var explanation = buildExplanation(classVals, data, issues, partyCombinations);
var explained = d3.select(".base-svg").append("text")
.attr("x", margin.left)
.attr("y", margin.top + height + (margin.bottom / 2))
.attr("text-anchor", "start")
.attr("id", "data-explanation")
.text(explanation);
})
.on("mouseout", function() {
d3.select(this).attr("fill-opacity", "1");
d3.selectAll("#data-explanation").remove();
});
row += 1;
});
var xAxis = d3.svg.axis().scale(xScale).orient("top");
var yAxis = d3.svg.axis().scale(yScale).orient("left");
x.call(xAxis);
y.call(yAxis);
});
var aspect = widthInit/heightInit,
graph = $(".base-svg");
$(window).on("resize", function() {
var targetWidth = graph.parent().width();
graph.attr("width", targetWidth);
graph.attr("height", targetWidth/aspect)
});
})();
</script>
</body>
</html>
[{
"partyCombinations": ["FrP, H","FrP, H, KrF","FrP, H, KrF, V","FrP, H, V","FrP, KrF","FrP, KrF,V ","FrP, V","H, KrF","H, KrF, V","H, V","KrF, V"],
"issues": [
"Atomkraft",
"Skatteavtaler",
"Riksrevisjonen",
"Traktater",
"Stortingsrepresentanter",
"Stortingets forretningsorden",
"Kartverk",
"Grunnloven",
"Forsvar",
"Statsforfatning",
"Spesialundervisning",
"Vassdragsregulering",
"Regjeringen",
"Økonomisk samarbeid",
"Nordisk samarbeid"
],
"data": [
{
"issue": "Atomkraft",
"values": [100,100,100,100,100,100,100,100,100,100,100]
},
{
"issue": "Skatteavtaler",
"values": [98.3,98.3,98.3,98.3,98.3,98.3,98.3,100,100,100,100]
},
{
"issue": "Riksrevisjonen",
"values": [97.6,97.6,97.6,97.6,97.6,97.6,97.6,100,100,100,100]
},
{
"issue": "Traktater",
"values": [97.4,97.4,97.4,97.4,97.4,97.4,97.4,100,100,100,100]
},
{
"issue": "Stortingsrepresentanter",
"values": [92.7,92.7,92.7,92.7,92.7,92.7,92.7,100,100,100,100]
},
{
"issue": "Stortingets forretningsorden",
"values": [92.3,92.3,92.3,92.3,92.3,92.3,92.3,100,100,100,100]
},
{
"issue": "Kartverk",
"values": [88,65.8,44.3,44.9,67.1,45.6,46.2,76.6,55.1,55.7,77.8]
},
{
"issue": "Grunnloven",
"values": [84.2,71.1,43.4,48.7,75,47.4,55.3,82.9,52.6,57.9,64.5]
},
{
"issue": "Forsvar",
"values": [83.8,60.8,53.1,55.4,62.2,54,57.7,75.6,66.5,69.3,88.1]
},
{
"issue": "Statsforfatning",
"values": [83.2,70.1,62.7,67,73.4,64.2,70,83.5,74.6,80.8,84.9]
},
{
"issue": "Spesialundervisning",
"values": [82.7,57.3,56.4,66.4,60,59.1,70,71.8,70,80,88.2]
},
{
"issue": "Vassdragsregulering",
"values": [81.4,61.8,41.4,43.6,65.5,43.2,47.3,76.8,54.5,58.6,73.6]
},
{
"issue": "Regjeringen",
"values": [80.3,63.8,55.9,60.6,67.8,57.5,64.4,79.4,69.5,76.7,82.9]
},
{
"issue": "Økonomisk samarbeid",
"values": [80,27.9,20,34.7,28.9,20.5,37.4,46.8,36.8,52.1,74.7]
},
{
"issue": "Nordisk samarbeid",
"values": [79.6,37.7,28.8,33.5,38.7,29.3,36.1,57.1,46.1,51.3,83.8]
}
]
}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment