(This chart is a part of the d3-charts
collection available here.)
This chart provides a historical view of the---at times---shifting majorities of the two parliamentary coalitions in the polls. Due to the dull nation of Danish politics, the majority has been very one-sided this term.
Compare this to Sam Wang's chart for the U.S. 2012 election.
The chart currently uses a LOESS regression to create a trend line---partly to alleviate the noise from a heavy dataset with a lot of polls and pollsters.
NB: Be aware that this chart does not currently take vote thresholds into account; if a party falls below it, its vote share will still be added to its coalition tally. Most, if not all, parliamentary coalition-majority charts do not account for this, but that is no excuse creating the impression of a non-existent parliamentary majority.
All parameters are in the top of the script under // Config
. If we use the example script:
var dataset = "https://data.ndarville.com/danish-polls/data.csv", // "data.csv"
parseDate = d3.time.format("%Y-%m-%d").parse,
dateValue = "Date",
lastElectionDate = "2011-09-15", // ""
nextElectionDate = "2015-09-14", // ""
coalitionLeft = ["A", "B", "F", "Ø", "Å"],
coalitionLeftColor = "#2B83BA", // accessible red
coalitionRight = ["V", "O", "K", "I", "C"],
coalitionRightColor = "#D7191C", // accessible blue
yAxisTitle = "Votes (50%)",
loessRegression = true, // false
loessBandwidth = .1; // .2
// On a smaller dataset, you may want a bandwidth of .2
parseDate
refers to the date format---in this case YYYY-MM-DD
.
lastElectionDate
and nextElectionDate
refer to the date of the past and future election, if known. This will change the minimum and maximum on the x-axis to said date to contextualize the trends.
coalitionLeft/coalitionRight
refer to the letters of each member party of a coalition, entered as strings in an array.
loessRegression
and loessBandwidth
should just be left as-is, unless you're a total statistical-regression dork.
You can experiment with changing loessBandwidth
to .2
for a smoother trend.
The other parameters should be self-explanatory.
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
svg {
display: block;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
.x.axis path {
display: none;
}
.election-date-line {
stroke: #000;
stroke-width: 1px;
stroke-dasharray: 5,5;
}
.area {
zoom: 1;
filter: alpha(opacity=50);
opacity: .5;
}
#graph {
/** Layout breaks when minima are removed */
min-width: 440px;
min-height: 150px;
width: 100%;
height: 100%;
max-width: 440px;
max-height: 150px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id="graph"></div>
<!-- <script src="https://d3js.org/d3.v3.min.js"></script> -->
<script src="d3.min.js?v=3.5.5"></script>
<script src="science.v1.min.js"></script>
<script type="text/javascript" charset="utf-8">
// Settings
// ========
var defaultWidth = 440,
defaultHeight = 150,
aspectRatio = defaultWidth/defaultHeight;
var wrapperWidth = parseInt(d3.select("#graph").style("width"), 10),
wrapperHeight = parseInt(d3.select("#graph").style("height"), 10);
var padding = 30,
margin = {
"top" : padding,
"right" : 35,
"bottom" : padding,
"left" : padding
};
// If landscape mode or square
if (wrapperWidth >= wrapperHeight) {
var height = wrapperHeight,
width = Math.round(height * aspectRatio);
// If protrait mode
} else {
var width = wrapperWidth,
height = Math.round(width / aspectRatio);
}
margin.hor = margin.left + margin.right;
margin.ver = margin.top + margin.bottom;
// Config
// ======
var dataset = "https://data.ndarville.com/danish-polls/data.csv", // "data.csv"
parseDate = d3.time.format("%Y-%m-%d").parse,
dateValue = "Date",
lastElectionDate = "2011-09-15", // ""
nextElectionDate = "2015-06-18", // ""
coalitionLeft = ["A", "B", "F", "Ø", "Å"],
coalitionLeftColor = "#D7191C", // Accessible red
coalitionRight = ["V", "O", "K", "I", "C"],
coalitionRightColor = "#2B83BA", // Accessible blue
yAxisTitle = "Votes (%)",
loessRegression = true, // false
loessBandwidth = .1; // .2
// On a smaller dataset, you may want a bandwidth of .2
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function(d) {
var timeFormat = d3.time.format("%-b")(d) === "Jan" ? "%Y" : "%-b";
return d3.time.format(timeFormat)(d);
});
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) {
return d - 50 === 0 ? "50%" : "+" + (d - 50);
});
var area = d3.svg.area()
.interpolate("linear")
.x(function(d) { return x(d[dateValue]); })
.y1(function(d) { return y(d["right"]); }); // ! Still breaks without this setting
var svg = d3.select("#graph").append("svg")
.attr({
// "width" : width + margin.hor,
// "height" : height + margin.ver,
"viewBox": "0 0 " + (width + margin.hor) + " " + (height + margin.ver),
"preserveAspectRatio": "xMinYMid"
})
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv(dataset, function(error, data) {
data.forEach(function(d) {
d[dateValue] = parseDate(d[dateValue]);
coalitionSum = function(d, coalition) {
var votes = 0;
for (var i = 0; i < coalition.length; i++) {
// Add the votes for each coalition party to total
votes += +d[coalition[i]] || 0;
}
return votes;
};
d["left"] = coalitionSum(d, coalitionLeft);
d["right"] = coalitionSum(d, coalitionRight);
d["total"] = d["left"] + d["right"];
});
x.domain([
parseDate(lastElectionDate) || d3.min(data, function(d) { return d[dateValue]; }),
parseDate(nextElectionDate) || d3.max(data, function(d) { return d[dateValue]; })
]);
data = data.filter(function(d) {
// Filter data by date range
return x.domain()[0] <= d[dateValue] && d[dateValue] <= x.domain()[1]; })
.sort(function(a, b) {
// Non-descending date chronology breaks loess(),
// so we make sure to order the data properly.
return a[dateValue] - b[dateValue];
});
if (loessRegression === true) {
// LOESS trend
// ===========
var loess = science.stats.loess().bandwidth(loessBandwidth);
var dateArr = data.map(function(d) { return d[dateValue]; }),
leftArr = data.map(function(d) { return d["left"]; }),
rightArr = data.map(function(d) { return d["right"]; });
var leftLOESS = loess(dateArr, leftArr),
rightLOESS = loess(dateArr, rightArr);
data.forEach(function(d, i) {
d["left"] = leftLOESS[i];
d["right"] = rightLOESS[i];
});
}
y.domain([
50, d3.max(data, function(d) { return Math.max(d["right"], d["left"]); })
]);
svg.datum(data);
// Above line
svg.append("clipPath")
.attr("id", "clip-below")
.append("path")
.attr("d", area.y0(height));
// Below line
svg.append("clipPath")
.attr("id", "clip-above")
.append("path")
.attr("d", area.y0(0));
// Above area
svg.append("path")
.attr({
"class" : "area above",
"clip-path" : "url(#clip-above)",
"d" : area
.y0(function(d) { return y(d["left"]); })
.y1(y(50)),
"fill" : coalitionLeftColor
});
// Below area
svg.append("path")
.attr({
"class" : "area below",
"clip-path" : "url(#clip-below)",
"d" : area
.y0(y(0))
.y1(function(d) { return y(d["right"]); }),
"fill" : coalitionRightColor
});
// X-axis
// ======
svg.append("g")
.attr({
"class" : "x axis",
"transform" : "translate(0," + height + ")"
})
.call(xAxis);
// Y-axis
// ======
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr({
"transform" : "rotate(-90)",
"y" : 6,
"dy" : ".71em"
})
.style("text-anchor", "end")
.text(yAxisTitle);
// Line displaying election date
// =============================
if (nextElectionDate) {
svg.append("line")
.attr({
"class" : "election-date-line",
"x1" : x(parseDate(nextElectionDate)),
"x2" : x(parseDate(nextElectionDate)),
"y1" : y.range()[0],
"y2" : y.range()[1]
});
}
});
</script>
</body>
</html>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js