Skip to content

Instantly share code, notes, and snippets.

@borgar
Last active October 30, 2016 21:41
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save borgar/b99c8a915e1745b3d8ed4e25a34b6b98 to your computer and use it in GitHub Desktop.
Possible majority coalitions
license: mit
height: 500
border: no

For the last few decades the convention in the Alþingi has largely been a 2-party coalition majority government. The vote has split between 4 major parties two of whom then have a majority.

Prior to elections polls are always displayed by the media as either a line-chart of percentages over time, or a bar-chart of the current percentage by party. This works for situations where there are few parties and so it is easy to see who can form a majority.

But things are changing. 7 parties will in all likelyhood get parlimentary seats so it becomes quite challenging to understand what majority posibilities exist post-election.

What this attempts to do is show what majority coalitions are possible using 2016 election results, ruling out coalitions which been rejected by the parties themselves. What remains indicates that forming a viable majority will be challenging.

id party color prection_percent percent seats
A Björt framtíð #a151e9 6.9 7.2 4
B Framsókn #b8d887 10 11.5 8
C Viðreisn #62b8e7 9.9 10.5 7
D Sjálfstæðisfl. #517cb6 24.9 29.0 21
P Píratar #7f7f7f 19.4 14.5 10
S Samfylkingin #cf5163 6.5 5.7 3
V Vinstri græn #7ca772 16.5 15.9 10
<!DOCTYPE html>
<meta charset='utf-8'>
<style>
svg {
font: 11px sans-serif;
}
.header {
text-anchor: start;
font: bold 16px sans-serif;
}
.legend {
text-anchor: start;
font: 13px sans-serif;
}
.legend-r {
text-anchor: end;
font: 13px sans-serif;
}
rect.seat {
stroke-width: .5;
}
line {
shape-rendering: crispEdges;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="util.js"></script>
<script>
function toSeats ( lineup ) {
var _lineup = [];
lineup.forEach(d => {
for (var i=0; i<parties[d].seats; i++) {
_lineup.push({ id: d, index: _lineup.length });
}
});
while (_lineup.length < total_mps) {
_lineup.push({ id: '?', index: _lineup.length });
}
return _lineup;
}
var margin = { top: 20, right: 10, bottom: 25, left: 3 },
width = 960 - margin.left - margin.right,
height = 1500 - margin.top - margin.bottom,
cx = width / 2;
var total_mps = 63;
var half_mps = total_mps / 2;
var parties = {
'?': {
id: '?',
party: 'Aðrir',
// color: '#bbb',
percent: 0,
}
};
var common_coalitions = [
['B','D'], // núverandi stjórn
['S','V','A'], // vinstri stjórn
['D','C','B'], // hægri stórn
['A','S','V','P'], // stjórnarandstaðan
['A','C','S','P'], //
];
var svg = d3.select( 'body' ).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})` );
svg.append('defs')
.append('pattern')
.attr('id', 'stripes')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 4)
.attr('height', 4)
.append('path')
.attr('d', "M0,4 l4,-4 M-1,1 l2,-2 M3,5 l2,-2")
.attr('stroke-width', 1)
.attr('shape-rendering', 'auto')
.attr('stroke', '#aaa')
.attr('stroke-linecap', 'square');
d3.csv('data.csv', function ( err, data ) {
data.forEach(d => {
d.count = +d.count;
d.percent = +d.percent;
d.prection_percent = +d.prection_percent;
d.seats = +d.seats;
parties[d.id] = d;
});
// dhont( data, total_mps );
var sections = [];
var pool = d3.comb(Object.keys(parties))
.map(lineup => {
var comb = obj(lineup);
// These have declared they won't work together
// http://stundin.is/frett/piratar-utiloka-framsoknarflokkinn-og-sjalfstaedis/
if (('P' in comb && ('D' in comb || 'B' in comb)) ||
('D' in comb && ('V' in comb || 'S' in comb))) {
return null;
}
var id = lineup.sort(d3.ascending).join(''),
counts = lineup.map(d => parties[d].seats).sort(d3.descending),
mps = d3.sum(counts),
but_last = d3.sum(counts.slice(0,-1));
if (but_last > half_mps) { return null; }
if (mps && mps > half_mps) {
lineup = lineup.sort((a,b) => d3.descending(parties[a].percent, parties[b].percent));
return {
'id': id,
'mps': mps,
'seats': toSeats(lineup),
'lineup': lineup,
};
}
})
.filter(Boolean)
.sort((a,b) => b.mps - a.mps);
sections.push({
'offset': 0,
'title': "Mögulegar meirihlutastjórnir",
'combinations': pool,
});
var runners_up = common_coalitions
.map(lineup => {
var id = lineup.sort(d3.ascending).join('');
if (pool.filter( d=> d.id === id).length) {
// already exists in pool
return null;
}
lineup = lineup.sort((a,b) => d3.descending(parties[a].percent, parties[b].percent));
return {
'id': id,
'mps': d3.sum(lineup, d => parties[d].seats),
'seats': toSeats(lineup),
'lineup': lineup,
};
});
sections.push({
'offset': d3.sum(sections, d => d.combinations.length + 1),
'title': 'Pólitískur ómöguleiki',
'combinations': runners_up.filter(Boolean),
});
// reconfigure chart height:
height = d3.sum(sections, d => 1 + d.combinations.length) * 45;
svg.attr('height', height + margin.top + margin.bottom);
console.log( height );
svg.append('rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'white');
var x = d3.scale.linear()
.domain([ 0, total_mps ])
.range([ 0, Math.floor(width/total_mps) * total_mps ]);
var y = d3.scale.ordinal()
.domain(pool.map(d => d.id))
.rangeBands([ 0, pool.length * 45 ]);
var sects = svg.selectAll('.section')
.data(sections).enter()
.append('g')
.attr('class', 'section')
.attr('transform', d => `translate(0,${(1+d.offset) * 45})`);
sects.append('text')
.attr('class', 'header')
.attr('y', -30)
.text(d => d.title);
var stacks = sects.selectAll('.stack')
.data(d => d.combinations).enter()
.append('g')
.attr('class', 'stack')
.attr('transform', (d, i) => `translate(0,${i * 45})`);
stacks.append('text')
.attr('class', 'legend')
.attr('dy', -5)
.text(d => {
var t = d.lineup.map(d => parties[d].party).join(', ');
return d.mps + ' þingsæti: ' + t + '.';
});
stacks.append('text')
.attr('class', 'legend-r')
.attr('x', x(total_mps))
.attr('dy', -5)
.text(d => {
return (d.mps < half_mps)
? `Vantar ${-Math.floor(d.mps - half_mps)} þingsæti.`
: `${(d.mps - half_mps)*2} sæta meirihluti.`;
});
var bars = stacks.selectAll('.seat')
.data(d => d.seats).enter();
bars.append('rect')
.attr('class', 'seat')
.attr('x', d => x(d.index))
.attr('height', x(1))
.attr('width', x(1))
.attr('fill', d => parties[d.id].color || 'url(#stripes)')
.attr('stroke', 'white');
bars.append('circle')
.attr('class', 'empty-seat')
.attr('cy', x(1) / 2)
.attr('cx', d => x(d.index) + x(1) / 2)
.attr('r', x(1) / 5)
.attr('fill', d => {
if ( d.index < half_mps && d.id === '?' ) {
return 'gray';
}
if ( d.index > half_mps - 1 && d.id !== '?' ) {
return 'white';
}
return 'none';
})
.attr('stroke', d => {
if ( d.index < half_mps && d.id === '?' ) {
return 'white';
}
if ( d.index > half_mps - 1 && d.id !== '?' ) {
return 'gray';
}
return 'none';
});
});
</script>
d3.comb = function ( arr ) {
function _cmb ( active, rest, comb ) {
if ( active.length && !rest.length ) {
comb.push( active );
}
else if ( active.length || rest.length ) {
var _rest = rest.slice( 1 );
_cmb( active.concat([ rest[0] ]), _rest, comb );
_cmb( active, _rest, comb );
}
return comb;
}
return _cmb( [], arr, [] );
};
function dhont ( parties, total_seats ) {
var quot = d => d.percent / ( d.seats + 1 );
parties.forEach(d => { d.seats = 0; });
for (var round=0; round<total_seats; round++) {
var top = parties.sort((a, b) => quot(b) - quot(a) )[0];
top.seats++;
}
}
function obj ( k, acc ) {
var r = {};
k.forEach(d => r[d] = acc ? acc.call(k,d) : true);
return r;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment