Skip to content

Instantly share code, notes, and snippets.

@gka
Last active December 11, 2015 11:49
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 gka/13ea926f0c2a73492a9e to your computer and use it in GitHub Desktop.
Save gka/13ea926f0c2a73492a9e to your computer and use it in GitHub Desktop.
coffeescript source code of the animated coalition bar chart
# coalition stacked bar chart
$ () ->
_coalitions = ["CDU,SPD", "CDU,FDP", "CDU,GRÜNE", "SPD,GRÜNE", "SPD,FDP", "SPD,LINKE", "CDU,FDP,GRÜNE", "SPD,FDP,GRÜNE", "SPD,LINKE,GRÜNE"]
_cont = $ '#canvas'
_lblcont = $ '.vis-coalitions'
canvas = Raphael _cont.get 0
bar_w = 30
elections = null
width = _cont.width()
height = _cont.height()-12
stacked_bars = {}
grid = {}
bg = null
partyColors = Common.partyColors
winner =
'13': 'SPD,GRÜNE'
'08': 'CDU,FDP'
'03': 'CDU,FDP'
'98': 'SPD'
'94': 'SPD'
'90': 'SPD,GRÜNE'
$.fn.set = (txt) ->
$('span', this).html txt
get_coalitions = (election, justParties=false) ->
coalitions = []
election.min_seats = Math.ceil((election.s + 0.5) * 0.5)
$.each _coalitions, (i) ->
coalition =
id: i
key: _coalitions[i]
parties: []
seats: 0
$.each _coalitions[i].split(','), (p, party) ->
if election.result[party] and election.result[party].s > 0 # if party has results
coalition.parties.push # add it to coalition
name: party
seats: election.result[party].s
# and add their seats to the coalition seats
coalition.seats += election.result[party].s
else
coalition = null
return false
if coalition?
# sort parties in coalition by seats
coalition.parties.sort (a,b) ->
b.seats - a.seats
coalitions.push coalition
# filter coalitions that make no sense
coalitions = coalitions.filter (coalition) ->
if coalition.parties.length == 3
makessense = true
$.each coalitions, (i, c) ->
if c.parties.length == 2
if c.parties[0].name == coalition.parties[0].name
if c.parties[1].name == coalition.parties[1].name or c.parties[1].name == coalition.parties[2].name
if c.seats > election.min_seats
makessense = false
return false
makessense
else
true
if justParties
coalitions = []
# add individual parties for comparison
$.each election.result, (party) ->
if election.result[party].s > election.min_seats * 0.5 or (justParties and election.result[party].s > 0)
coalitions.push
key: party
seats: election.result[party].s
parties: [{ name: party, seats: election.result[party].s }]
# sort coalitions by majority
coalitions.sort (a,b) ->
if a.seats >= election.min_seats && b.seats >= election.min_seats
# sort by biggest party first, bc they have the
# privilege to set a government
if a.parties[0].seats != b.parties[0].seats
return b.parties[0].seats - a.parties[0].seats
b.seats - a.seats;
# return coalitions
coalitions
render = (election_index, justParties=false) ->
election = elections[election_index]
coalitions = get_coalitions election, justParties
bar_w = if justParties then 60 else 30
yscale = (seats) ->
# scale according to biggest coalition seats
Math.round seats / coalitions[0].seats * height
label = (clss, txt) ->
lbl = $ '<div class="label '+clss+'"><span /></div>'
_lblcont.append lbl
lbl.css
opacity: 0
if txt?
lbl.set txt
lbl
if not justParties
$('.desc').hide()
setTimeout () ->
$('.desc').fadeIn 1000
, 2000
$('.label.top').animate
opacity: 0
bar_w = Math.round((width - 220) / coalitions.length / 1.8)
offset = width - coalitions.length * bar_w * 1.8 - 15
if justParties
bar_w = (width - 340) / coalitions.length / 1.8
# animate bars for each coalition
possible = true
$.each coalitions, (i, coalition) ->
if not stacked_bars[coalition.key]?
# first occurance of this coalition, create bars
sbc = stacked_bars[coalition.key] = {}
$.each coalition.parties, (j, party) ->
x = 20 + i * bar_w * 1.8
x += 340 if justParties
y = 0
h = 0
bar = canvas.rect x, height - h - y, bar_w, h
bar.attr
stroke: if coalition.seats > election.min_seats then '#fff' else '#eee'
opacity: 0.98
fill: partyColors[party.name] || ('#ccc' && console.log(party.name))
sbc[party.name] = bar
sbc.toplabel = label 'bar top center'
sbc.bottomlabel = label 'bar bottom center', coalition.key.replace(/,/g, '<br/>')
sbc = stacked_bars[coalition.key]
# position the stacked bars
y = 0
x = Math.round 20 + i * bar_w * 1.8
if coalition.seats < election.min_seats
x += offset if not justParties
if possible
$('.desc-impossible').css
right: width - x+10
$('.desc-possible').css
left: x - offset
possible = false
if justParties
# move to the right
x += 340
setTimeout () ->
sbc.bottomlabel.animate
left: x-40+bar_w*0.5
sbc.toplabel.animate
left: x-40+bar_w*0.5
, 1000
# make labels visible again, in case they're hidden
setTimeout () ->
sbc.bottomlabel.animate
opacity: 1
sbc.toplabel.animate
opacity: 1
, if justParties then 1000 else 2000
$.each coalition.parties, (j, party) ->
bar = sbc[party.name]
# animate bar heights and y position first
h = yscale party.seats
barprops =
y: height - h - y - 0.5
height: h
width: bar_w
bar.animate barprops, 1000, 'expoInOut', () ->
props = # animate stacks x position last
bar.animate
x: x + 0.5
, 1000, 'expoInOut'
# make bar visible again, it case it was hidden
setTimeout ()->
bar.animate
opacity: 1
, 300
, 1000
y += h
sbc.toplabel.animate
top: height - y - 22
if justParties
tl = coalition.seats
sbc.toplabel.removeClass 'negative'
else
tl = coalition.seats - election.min_seats
if tl < 0
sbc.toplabel.addClass 'negative'
else
sbc.toplabel.removeClass 'negative'
tl = '+' + tl if tl > 0
tl = '±' + tl if tl == 0
sbc.toplabel.set tl
$('.label.bottom').removeClass 'winner'
setTimeout () ->
if not justParties
if winner[election.yr] and stacked_bars[winner[election.yr]]
stacked_bars[winner[election.yr]].bottomlabel.addClass 'winner'
, 2200
# hide previous coalitions
$.each stacked_bars, (key, bars) ->
found = false
$.each coalitions, (i, coalition) ->
if coalition.key == key
found = true
false
true
if not found
$.each bars, (party, bar) ->
bar.animate
opacity: 0
, 300
# update grid lines
init_grid_line = (addlbl=true) ->
line = canvas.path 'M0,'+(height-.5)+' L'+width+','+(height-0.5)
line.toBack()
if addlbl
lbl = label 'grid left'
line.data 'lbl', lbl
lbl = label 'grid right'
line.data 'lbl2', lbl
line
move_grid_line = (line, seats) ->
animprops =
transform: 't0,'+(0 - yscale(seats))
line.animate animprops, '800', 'expoInOut'
if line.data('lbl')?
lbl = line.data 'lbl'
lbl.set seats
lbl.animate
opacity: 1
top: height - yscale(seats) - 8
lbl = line.data 'lbl2'
lbl.set seats
lbl.animate
opacity: 1
top: height - yscale(seats) - 8
return
if not grid.min_seats?
grid.min_seats = init_grid_line()
grid.min_seats.data('lbl').addClass 'min-seats'
grid.min_seats.data('lbl2').addClass 'min-seats'
# initialize base line
grid.bottom = init_grid_line(false) #.transform 't0,1'
grid.bottom.toFront()
move_grid_line grid.min_seats, election.min_seats
# init and move grid lines
$.each [20,40,60,80,100], (i,seats) ->
if not grid[seats]?
grid[seats] = init_grid_line()
grid[seats].attr
'stroke-dasharray': '- '
move_grid_line grid[seats], seats
lineprops =
opacity: if seats+5 < election.min_seats then 1 else 0
grid[seats].animate lineprops, 500
grid[seats].data('lbl').animate lineprops
grid[seats].data('lbl2').animate lineprops
# init and resize background
if not bg?
bg = canvas.rect 0,height-1,width,1
bg.attr
fill: '#f5f5f5'
stroke: false
bg.toBack()
bgprops =
y: height - yscale(election.min_seats)
height: yscale(election.min_seats)
opacity: 1
if justParties
$('.desc-intro').show()
$('.desc-impossible, .desc-possible, .label.left').hide()
bg.animate
opacity: 0
else
$('.desc-intro').hide()
$('.desc-impossible, .desc-possible, .label.left').show()
bg.animate bgprops, 800, 'expoInOut'
if location.hash.length and location.hash != '#activate'
progn = location.hash.substr(1).replace(/\//, '-')
else
#progn = $($('.prognosen a').get(0)).attr('href').substr(1)
progn = 'elections-nds-amtl-2342'
$('.prognosen a[href=#'+progn+']').addClass 'active'
$.ajax
url: '/assets/data/'+progn+'.json'
dataType: 'json'
.done (json) ->
elections = json
active = elections.length-1
elections[active].s = 0
for p of elections[active].result
elections[active].s += Number(elections[active].result[p].s)
justParties = location.hash.length == 0
elsel = Common.ElectionSelector elections, active
, (active) -> # click callback
$('.prognosen a').removeClass 'active'
render active, justParties
true
, (e) -> # function that extracts year
e.yr
$('[href=#activate]').click ()->
justParties = false
render active, justParties
render active, justParties
blocked = false
$('.prognosen a').click (evt) ->
if blocked
return
blocked = true
evt.preventDefault()
a = $(evt.target)
prog = a.attr('href').substr(1)
$('.prognosen a').removeClass 'active'
$.getJSON '/assets/data/' + prog + '.json', (data) ->
latest = data[data.length-1]
different = false
latest.s = 0
for p of latest.result
if elections[elections.length-1].result[p].s != latest.result[p].s
different = true
latest.s += Number(latest.result[p].s)
elections[elections.length-1] = latest
if different
render active, justParties
a.addClass 'active'
location.hash = '#' + prog
blocked = false
# location.href = '/koalitionen/' + $(evt.target).attr('href')
# location.reload()
[{
"d": 100,
"eligable-voters": 5712613,
"voters": 4263215,
"i": [58983, 46919],
"s": 155,
"result": {
"FBPD": {
"s": 0,
"d": 0,
"v": [81, 0]
},
"Bewu\u00dftsein": {
"s": 0,
"d": 0,
"v": [0, 632]
},
"CM": {
"s": 0,
"d": 0,
"v": [90, 1367]
},
"FDP": {
"s": 9,
"d": 0,
"v": [213425, 252615]
},
"DS": {
"s": 0,
"d": 0,
"v": [0, 525]
},
"NPD": {
"s": 0,
"d": 0,
"v": [9396, 8255]
},
"REP": {
"s": 0,
"d": 0,
"v": [622, 62054]
},
"FVP": {
"s": 0,
"d": 0,
"v": [0, 309]
},
"Die Unabh\u00e4ngigen": {
"s": 0,
"d": 0,
"v": [5737, 2739]
},
"\u00d6KO-UNION": {
"s": 0,
"d": 0,
"v": [0, 3682]
},
"CDU": {
"s": 67,
"d": 39,
"v": [1811352, 1771974]
},
"Familie": {
"s": 0,
"d": 0,
"v": [531, 4529]
},
"PBC": {
"s": 0,
"d": 0,
"v": [0, 3858]
},
"Einzelbewerber": {
"s": 0,
"d": 0,
"v": [208, 0]
},
"Patrioten": {
"s": 0,
"d": 0,
"v": [421, 650]
},
"DDD": {
"s": 0,
"d": 0,
"v": [0, 1126]
},
"SPD": {
"s": 71,
"d": 61,
"v": [1943278, 1865267]
},
"DRD": {
"s": 0,
"d": 0,
"v": [0, 2705]
},
"DP": {
"s": 0,
"d": 0,
"v": [0, 560]
},
"GR\u00dcNE": {
"s": 8,
"d": 0,
"v": [213114, 229846]
}
},
"v": [4204232, 4216296],
"yr": "90"
}, {
"d": 100,
"eligable-voters": 5851720,
"voters": 4316428,
"i": [85427, 67407],
"s": 161,
"result": {
"\u00d6DP": {
"s": 0,
"d": 0,
"v": [3093, 4347]
},
"REP": {
"s": 0,
"d": 0,
"v": [95902, 159026]
},
"STATT Partei": {
"s": 0,
"d": 0,
"v": [43803, 55605]
},
"FDP": {
"s": 0,
"d": 0,
"v": [174743, 188691]
},
"DP": {
"s": 0,
"d": 0,
"v": [2263, 0]
},
"NEUE STATT PARTEI": {
"s": 0,
"d": 0,
"v": [0, 19361]
},
"LLN": {
"s": 0,
"d": 0,
"v": [1429, 8176]
},
"SPD": {
"s": 81,
"d": 81,
"v": [1971551, 1880623]
},
"WIR": {
"s": 0,
"d": 0,
"v": [136, 0]
},
"CM": {
"s": 0,
"d": 0,
"v": [1232, 0]
},
"NATURGESETZ": {
"s": 0,
"d": 0,
"v": [431, 7325]
},
"PBC": {
"s": 0,
"d": 0,
"v": [908, 8152]
},
"Einzelbewerber": {
"s": 0,
"d": 0,
"v": [4411, 0]
},
"B\u00fcSo": {
"s": 0,
"d": 0,
"v": [456, 0]
},
"NPD": {
"s": 0,
"d": 0,
"v": [2562, 9430]
},
"MITTE": {
"s": 0,
"d": 0,
"v": [5155, 4123]
},
"Die Unabh\u00e4ngigen": {
"s": 0,
"d": 0,
"v": [4000, 4906]
},
"\u00d6KO-UNION": {
"s": 0,
"d": 0,
"v": [1541, 7902]
},
"CDU": {
"s": 67,
"d": 19,
"v": [1610098, 1547610]
},
"UWN": {
"s": 0,
"d": 0,
"v": [11140, 8819]
},
"GRAUE": {
"s": 0,
"d": 0,
"v": [2304, 20581]
},
"GR\u00dcNE": {
"s": 13,
"d": 0,
"v": [293837, 314344]
}
},
"v": [4231001, 4249021],
"yr": "94"
}, {
"d": 100,
"eligable-voters": 5929243,
"voters": 4376643,
"i": [80051, 61711],
"s": 157,
"result": {
"\u00f6dp": {
"s": 0,
"d": 0,
"v": [2587, 4730]
},
"DKP": {
"s": 0,
"d": 0,
"v": [1331, 8597]
},
"CDU": {
"s": 62,
"d": 17,
"v": [1647814, 1549227]
},
"REP": {
"s": 0,
"d": 0,
"v": [41557, 118975]
},
"STATT Partei": {
"s": 0,
"d": 0,
"v": [29727, 30224]
},
"PDS": {
"s": 0,
"d": 0,
"v": [6504, 0]
},
"DIE FRAUEN": {
"s": 0,
"d": 0,
"v": [0, 6775]
},
"SFP": {
"s": 0,
"d": 0,
"v": [602, 0]
},
"FDP": {
"s": 0,
"d": 0,
"v": [143702, 209610]
},
"PBC": {
"s": 0,
"d": 0,
"v": [2724, 7984]
},
"Einzelbewerber": {
"s": 0,
"d": 0,
"v": [14948, 0]
},
"SPD": {
"s": 83,
"d": 83,
"v": [2090805, 2068477]
},
"DP": {
"s": 0,
"d": 0,
"v": [4087, 6140]
},
"GR\u00dcNE": {
"s": 12,
"d": 0,
"v": [310204, 304193]
}
},
"v": [4296592, 4314932],
"yr": "98"
}, {
"d": 100,
"eligable-voters": 6023636,
"voters": 4036017,
"i": [58812, 52008],
"s": 183,
"result": {
"\u00f6dp": {
"s": 0,
"d": 0,
"v": [0, 3671]
},
"FDP": {
"s": 15,
"d": 0,
"v": [176862, 323107]
},
"B\u00fcSo": {
"s": 0,
"d": 0,
"v": [1455, 0]
},
"REP": {
"s": 0,
"d": 0,
"v": [1152, 17043]
},
"PDS": {
"s": 0,
"d": 0,
"v": [14605, 21560]
},
"CDU": {
"s": 91,
"d": 91,
"v": [2077689, 1925055]
},
"FAMILIE": {
"s": 0,
"d": 0,
"v": [868, 0]
},
"PBC": {
"s": 0,
"d": 0,
"v": [2984, 7819]
},
"Einzelbewerber": {
"s": 0,
"d": 0,
"v": [5197, 0]
},
"GRAUE": {
"s": 0,
"d": 0,
"v": [589, 10724]
},
"SPD": {
"s": 63,
"d": 9,
"v": [1441971, 1330156]
},
"Schill": {
"s": 0,
"d": 0,
"v": [19419, 40342]
},
"GR\u00dcNE": {
"s": 14,
"d": 0,
"v": [234414, 304532]
}
},
"v": [3977205, 3984009],
"yr": "03"
}, {
"d": 87,
"eligable-voters": 6087297,
"voters": 3476112,
"i": [61595, 50686],
"s": 152,
"result": {
"LINKE": {
"s": 11,
"d": 0,
"v": [217345, 243361]
},
"\u00f6dp": {
"s": 0,
"d": 0,
"v": [487, 1962]
},
"Die Weissen": {
"s": 0,
"d": 0,
"v": [519, 0]
},
"FDP": {
"s": 13,
"d": 0,
"v": [191840, 279826]
},
"NPD": {
"s": 0,
"d": 0,
"v": [26012, 52986]
},
"REP": {
"s": 0,
"d": 0,
"v": [897, 0]
},
"FW": {
"s": 0,
"d": 0,
"v": [31195, 17960]
},
"FAMILIE": {
"s": 0,
"d": 0,
"v": [381, 13325]
},
"CDU": {
"s": 68,
"d": 68,
"v": [1512779, 1456742]
},
"Die Tierschutzpartei": {
"s": 0,
"d": 0,
"v": [0, 17174]
},
"Volksabstimmung": {
"s": 0,
"d": 0,
"v": [0, 5934]
},
"PBC": {
"s": 0,
"d": 0,
"v": [568, 5851]
},
"Einzelbewerber": {
"s": 0,
"d": 0,
"v": [5033, 0]
},
"GRAUE": {
"s": 0,
"d": 0,
"v": [0, 9288]
},
"SPD": {
"s": 48,
"d": 19,
"v": [1183758, 1036727]
},
"Die Friesen": {
"s": 0,
"d": 0,
"v": [4122, 10069]
},
"GR\u00dcNE": {
"s": 12,
"d": 0,
"v": [239581, 274221]
}
},
"v": [3414517, 3425426],
"yr": "08"
}, {
"d": 87,
"eligable-voters": 6100218,
"voters": 3620994,
"i": [51980, 51980],
"s": 147,
"result": {
"CDU": {
"s": 54
},
"FDP": {
"s": 14
},
"LINKE": {
"s": 0
},
"SPD": {
"s": 49
},
"GR\u00dcNE": {
"s": 20
}
},
"v": [3414517, 3425426],
"yr": "13"
}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment