Skip to content

Instantly share code, notes, and snippets.

@karmi
Last active March 8, 2021 11:20
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karmi/4626240 to your computer and use it in GitHub Desktop.
Save karmi/4626240 to your computer and use it in GitHub Desktop.
.DS_Store
tmp/*
{
"candidates": {
"first_round": [
{
"id": "FIS",
"name": "Jan Fischer",
"original_votes": 841437,
"final_votes": 841437,
"new_voters_percent": 74,
"new_voters_split": 15
},
{
"id": "DIE",
"name": "Jiří Dienstbier",
"original_votes": 829297,
"final_votes": 829297,
"new_voters_percent": 78,
"new_voters_split": 80
},
{
"id": "FRA",
"name": "Vladimír Franz",
"original_votes": 351916,
"final_votes": 351916,
"new_voters_percent": 20,
"new_voters_split": 50
},
{
"id": "ROI",
"name": "Zuzana Roithová",
"original_votes": 255045,
"final_votes": 255045,
"new_voters_percent": 90,
"new_voters_split": 90
},
{
"id": "TAT",
"name": "Taťána Fischerová",
"original_votes": 166211,
"final_votes": 166211,
"new_voters_percent": 16,
"new_voters_split": 50
},
{
"id": "SOB",
"name": "Přemysl Sobotka",
"original_votes": 126846,
"final_votes": 126846,
"new_voters_percent": 86,
"new_voters_split": 87
},
{
"id": "BOB",
"name": "Jana Bobošíková",
"original_votes": 123171,
"final_votes": 123171,
"new_voters_percent": 19,
"new_voters_split": 50
}
],
"second_round": [
{
"id": "ZEM",
"name": "Miloš Zeman",
"original_votes": 1245848,
"final_votes": 2001804,
"new_voters_percent": 100,
"new_voters_split": 0
},
{
"id": "SCH",
"name": "Karel Schwarzenberg",
"original_votes": 1204195,
"final_votes": 2176753,
"new_voters_percent": 100,
"new_voters_split": 100
}
]
},
"created_at": "2013-01-23T13:44:12+00:00",
"not_voted": {
"id": "NOT_VOTED",
"name": "Nejde k volbám",
"original_votes": 3263856,
"final_votes": 0,
"new_voters_percent": 0,
"new_voters_split": 50
},
"updated_at": "2013-01-23T13:44:29+00:00"
}
{
"candidates": {
"first_round": [
{
"id": "FIS",
"name": "Jan Fischer",
"original_votes": 841437,
"final_votes": 841437,
"new_voters_percent": 29,
"new_voters_split": 50
},
{
"id": "DIE",
"name": "Jiří Dienstbier",
"original_votes": 829297,
"final_votes": 829297,
"new_voters_percent": 74,
"new_voters_split": 12
},
{
"id": "FRA",
"name": "Vladimír Franz",
"original_votes": 351916,
"final_votes": 351916,
"new_voters_percent": 28,
"new_voters_split": 50
},
{
"id": "ROI",
"name": "Zuzana Roithová",
"original_votes": 255045,
"final_votes": 255045,
"new_voters_percent": 91,
"new_voters_split": 88
},
{
"id": "TAT",
"name": "Taťána Fischerová",
"original_votes": 166211,
"final_votes": 166211,
"new_voters_percent": 20,
"new_voters_split": 50
},
{
"id": "SOB",
"name": "Přemysl Sobotka",
"original_votes": 126846,
"final_votes": 126846,
"new_voters_percent": 84,
"new_voters_split": 83
},
{
"id": "BOB",
"name": "Jana Bobošíková",
"original_votes": 123171,
"final_votes": 123171,
"new_voters_percent": 0,
"new_voters_split": 50
}
],
"second_round": [
{
"id": "ZEM",
"name": "Miloš Zeman",
"original_votes": 1245848,
"final_votes": 2019745,
"new_voters_percent": 100,
"new_voters_split": 0
},
{
"id": "SCH",
"name": "Karel Schwarzenberg",
"original_votes": 1204195,
"final_votes": 1758408,
"new_voters_percent": 100,
"new_voters_split": 100
}
]
},
"created_at": "2013-01-23T13:45:42+00:00",
"not_voted": {
"id": "NOT_VOTED",
"name": "Nejde k volbám",
"original_votes": 3263856,
"final_votes": 0,
"new_voters_percent": 0,
"new_voters_split": 50
},
"updated_at": "2013-01-23T13:45:42+00:00"
}
{
"candidates": {
"first_round": [
{
"id": "FIS",
"name": "Jan Fischer",
"original_votes": 841437,
"final_votes": 841437,
"new_voters_percent": 0,
"new_voters_split": 50
},
{
"id": "DIE",
"name": "Jiří Dienstbier",
"original_votes": 829297,
"final_votes": 829297,
"new_voters_percent": 0,
"new_voters_split": 50
},
{
"id": "FRA",
"name": "Vladimír Franz",
"original_votes": 351916,
"final_votes": 351916,
"new_voters_percent": 0,
"new_voters_split": 50
},
{
"id": "ROI",
"name": "Zuzana Roithová",
"original_votes": 255045,
"final_votes": 255045,
"new_voters_percent": 0,
"new_voters_split": 50
},
{
"id": "TAT",
"name": "Taťána Fischerová",
"original_votes": 166211,
"final_votes": 166211,
"new_voters_percent": 0,
"new_voters_split": 50
},
{
"id": "SOB",
"name": "Přemysl Sobotka",
"original_votes": 126846,
"final_votes": 126846,
"new_voters_percent": 0,
"new_voters_split": 50
},
{
"id": "BOB",
"name": "Jana Bobošíková",
"original_votes": 123171,
"final_votes": 123171,
"new_voters_percent": 0,
"new_voters_split": 50
}
],
"second_round": [
{
"id": "ZEM",
"name": "Miloš Zeman",
"original_votes": 1245848,
"final_votes": 1471053,
"new_voters_percent": 100,
"new_voters_split": 0
},
{
"id": "SCH",
"name": "Karel Schwarzenberg",
"original_votes": 1204195,
"final_votes": 1468567,
"new_voters_percent": 100,
"new_voters_split": 100
}
]
},
"created_at": "2013-01-23T13:47:37+00:00",
"not_voted": {
"id": "NOT_VOTED",
"name": "Nejde k volbám",
"original_votes": 3263856,
"final_votes": 489578,
"new_voters_percent": 15,
"new_voters_split": 54
},
"updated_at": "2013-01-23T13:47:37+00:00"
}
@font-face {
font-family: 'FontAwesome';
font-weight: normal;
font-style: normal;
src: url('https://raw.github.com/FortAwesome/Font-Awesome/master/font/fontawesome-webfont.woff') format('woff');
}
[class^="icon-"],
[class*=" icon-"] {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
display: inline;
width: auto;
height: auto;
line-height: normal;
vertical-align: baseline;
background-image: none;
background-position: 0% 0%;
background-repeat: repeat;
margin-top: 0;
}
.icon-remove-sign:before { content: "\f057"; }
.icon-link:before { content: "\f0c1"; }
.icon-share-alt:before { content: "\f064"; }
.icon-warning-sign:before { content: "\f071"; }
.icon-github:before { content: "\f09b"; }
.icon-comments:before { content: "\f086"; }
.icon-external-link:before { content: "\f08e"; }
body {
color: #222;
background: #fff;
font: normal 12px 'Open Sans', 'Helvetica Neue', Helvetica, sans-serif;
}
.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
.clearfix {display:block;}
header
{ color: #2b3233;
background: #efefef;
height: 210px;
width: 960px;
display: block;
border-bottom: 2px solid #fff;
padding: 5px 10px 10px 10px;
float: none; clear: both; }
header *
{ color: #2b3233 !important; }
header h1
{ font-family: 'Crete Round', serif;
font-size: 34px;
letter-spacing: -1px;
margin: 0; padding: 0; }
header p
{ font-family: 'Open Sans', sans-serif;
font-weight: 300;
font-size: 14px;
margin: 0 0 0.25em 0; padding: 0; }
header #scenarios
{ margin-top: 1em;
margin-bottom: 0.5em; }
header #scenarios section
{ padding: 0 10px 0 10px;
width: 225px;
height: 9em;
float: left;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABxCAIAAACbVV4fAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABtJREFUeNpiuHbtGhMDA8MoHpqY4f379wABBgA15QYuae8gywAAAABJRU5ErkJggg==);
position: relative;
}
header #scenarios section:first-child
{ background-image: none; }
header #scenarios section:first-child,
header #scenarios section:last-child
{ padding-left: 0;
width: 225px; }
header #scenarios section:last-child
{ padding-left: 10px;
padding-right: 0; }
header #scenarios h2
{ color: #1a1a1a;
font-size: 12px;
font-weight: 600;
margin: 0; padding: 0; }
header #scenarios p
{ font-size: 12px;
font-weight: 300;
margin: 0; padding: 0; }
header #scenarios p.buttons
{ position: absolute;
bottom: 0;
left: 4em; }
header #scenarios button
{ color: #444;
background: #c1c1c1;
font-family: 'Open Sans', sans-serif;
font-weight: 700;
text-align: center;
padding: 0.25em 1em;
border: none;
border-radius: 0.5em;
box-shadow: #666 0 1px 3px; }
header #scenarios button:hover
{ cursor: pointer; }
header #scenarios button:active
{ color: #222;
box-shadow: #999 0 0px 3px; }
.msie10 header #scenarios button
{ border-radius: 0 !important; }
.candidate rect
{ fill: #efefef;
stroke: #fff;
stroke-width: 2;
shape-rendering: crispEdges; }
.candidate circle
{ fill: #666;
stroke-width: 0; }
.candidate.winner circle
{ stroke: #999;
stroke-width: 0; }
.candidate circle.outline
{ display: none; }
.candidate.winner circle.outline
{ fill: #efefef;
display: inline; }
.candidate.winner.zem circle.outline
{ stroke: #b20335; }
.candidate.winner.sch circle.outline
{ stroke: #0372b2; }
.candidate text
{ font-family: 'Crete Round', serif;
font-weight: 300;
text-anchor: end;
text-shadow: white 0 1px 0; }
.candidate text.name
{ font-size: 14px; }
.candidate text.info
{ fill: #535353;
font-size: 12px; }
.candidate text.difference
{ fill: #1db10c;
font-size: 14px; }
.candidate text.percent
{ fill: #fff;
font-family: 'Open Sans', sans-serif;
font-weight: 600;
font-size: 14px;
text-shadow: #000 0 1px 0;
text-anchor: middle; }
.candidate.zem text.percent
{ text-shadow: #b20335 0 1px 1px; }
.candidate.sch text.percent
{ text-shadow: #0372b2 0 1px 1px; }
.candidate path.arc
{ fill: #70ff4f;
stroke: #efefef;
stroke-width: 2; }
.candidate path.arc.zem
{ fill: #b20335; }
.candidate path.arc.sch
{ fill: #0372b2; }
.candidate.zem circle
{ fill: #b20335; }
.candidate.sch circle
{ fill: #0372b2; }
.candidate.second_round text
{ text-anchor: middle; }
.candidate.second_round text.name
{ font-size: 22px; }
.candidate.second_round.zem text.name
{ fill: #b20335; }
.candidate.second_round.sch text.name
{ fill: #0372b2; }
.candidate.second_round text.info,
.candidate.second_round text.difference,
.not_voted text.name
{ font-size: 16px;
font-weight: 100; }
.not_voted circle.chart
{ fill: #617274; }
.not_voted text.name
{ font-size: 22px;
fill: #617274;
text-anchor: middle; }
.not_voted text.info
{ text-anchor: middle; }
#sliders
{ position: absolute;
left: 0;
top: 225px; }
div.sliders
{ position: absolute; }
.sliders input[type='range']
{ -webkit-appearance: none !important;
font-size: 9px;
background: #bfbfbf;
height: 7px;
border-radius: 2px;
border: none; }
.sliders input[type='range']::-webkit-slider-thumb
{ -webkit-appearance: none !important;
background: #666;
height: 10px;
width: 10px;
border-radius: 5px; }
.sliders input[type='range']::-webkit-slider-thumb:hover
{ background: #333; }
.sliders:hover input[type='range']::-webkit-slider-thumb,
.sliders input[type='range']::-webkit-slider-thumb:hover,
.sliders input[type='range']::-webkit-slider-thumb:active
{ height: 15px;
width: 15px;
border-radius: 10px; }
.sliders input[type='range']::-webkit-slider-thumb:hover,
.sliders input[type='range']::-webkit-slider-thumb:active
{ background: #333; }
.msie10 .sliders input[type='range']
{ margin: 0px 5px;
padding: 0px;
border: none; }
.msie10 .sliders input[type='range']::-ms-track
{ background-color: transparent;
color: transparent;
border: none; }
.msie10 .sliders input[type='range']::-ms-fill-lower
{background-color: #666; }
.sliders label
{ font-size: 10px;
display: inline-block; }
.sliders.participation label
{ width: 2em;
text-align: right;
display: inline-block; }
.sliders.participation svg polygon
{ stroke: #c1c1c1;
stroke-width: 2;
stroke-miterlimit: 10;
stroke-linejoin: round; }
.sliders.participation svg.min
{ margin-left: 0.55em; }
.sliders.participation svg.min polygon
{ fill: none; }
.sliders.participation svg.max polygon
{ fill: #c1c1c1; }
.sliders label.zem,
.sliders label.sch
{ color: #fff;
background: #999;
font-size: 9px;
padding: 0.25em 0.75em;
border-radius: 1em; }
.sliders label.zem
{ background: #b20335; }
.sliders label.sch
{ background: #0372b2; }
#photos
{ position: absolute;
left: 255px;
top: 615px;
width: 735px; }
#photos div
{ float: left;
margin-right: 2px; }
#photos div img
{ width: 242px;
height: 250px; }
#photos .about
{ color: 333;
background-color: #dedede;
padding: 10px;
width: 224px;
height: 230px;
margin-right: 0 !important; }
#photos .about h2
{ font-size: 14px;
font-weight: 300;
margin: 0 0 0.25em 0;
padding: 0; }
#photos .about p
{ font-size: 11px;
font-weight: 300;
margin: 0 0 0.3em 0;
padding: 0; }
#photos .about a
{ color: #333;
text-decoration: none; }
#photos .about .credentials
{ border-top: 1px solid #cacaca;
margin: 30px 0 0 0;
padding: 14px 0 0 0; }
#photos .about .credentials a
{ border-bottom: 1px solid #666; }
#photos .about .credentials a:hover
{ color: #222 !important;
border-color: #333; }
#app_save_data_popover
{ color: #444;
background: #fff;
padding: 1em;
border-radius: 0.25em;
box-shadow: #666 0 1px 3px;
z-index: 999;
width: 590px;
left: -530px;
top: 55px;
position: absolute;
display: none; }
#app_save_data_popover p
{ margin: 0; padding: 0; }
#app_save_data_popover .url
{ margin: 0.5em 0 1em 0;
padding: 0 0 1em 0;
border-bottom: 1px solid #c1c1c1; }
#app_save_data_popover a
{ text-decoration: none;
border: none !important; }
#app_save_data_popover a.open
{ padding-right: 1em; }
#app_save_data_popover a.open,
#app_save_data_popover a.close
{ float: right; }
#app_save_data_popover a.close
{ margin-right: 1em; }
#app_save_data_popover iframe.twitter-share-button
{ float: left; }
#app_save_data_popover a
{ padding: 0.25em 0.5em;
border-radius: 0.5em; }
#app_save_data_popover a:hover
{ background: #ccc; }
#app_save_data_popover .url a:hover
{ background: #fffa69 }
#app_save_data_popover [class^="icon-"],
#app_save_data_popover [class*=" icon-"]
{ margin-right: 4px; }
#app_save_data_popover .error,
#app_save_data_popover .error *
{ font-size: 14px;
color: #ac2409 !important; }
#overlay
{ background-color: #fff;
opacity: 0.7;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: none; }
var App = function(params) {
var url_params = function() {
var str = window.location.search;
var url_params = {};
str.replace(
new RegExp( "([^?=&]+)(=([^&]*))?", "g" ),
function( $0, $1, $2, $3 ){
url_params[ $1 ] = $3;
}
);
return url_params;
}()
var app = {
url: function() {
if ( window.location.protocol == 'file:' ) { return "default.json" }
if ( window.location.origin == 'http://bl.ocks.org' ) { return "default.json" }
if ( url_params['id'] ) { return '/api/' + url_params['id'] }
else { return '/default.json' }
}(),
locale: (params.locale || 'cz'),
origin_url: (params.origin_url || top.location.origin),
domain: (params.domain || document.domain)
};
d3.select("#application").classed("msie10", navigator.userAgent.match(/MSIE 10/))
d3.json(app.url + '?t=' + (new Date).getTime(), function(error, json) {
// Handler errors
//
if (error) {
var error_message = ( app.locale == 'en' ) ?
'<p class="error"><i class="icon-warning-sign"></i>We\'re sorry, the requested data cannot be loaded...</p>' :
'<p class="error"><i class="icon-warning-sign"></i>Litujeme, ale požadovaná data nelze načíst...</p>'
d3.select("#app_save_data_popover")
.style("opacity", 0)
.style("display", "block")
.html(error_message)
.transition()
.duration(150)
.style("opacity", 1)
throw(error)
}
// Setup variables and functions
//
app.json = json
app.data = app.json
app.panel_width = 245
app.panel_height = 90
app.margin = {top: 0, right: 0, bottom: 0, left: 0}
app.height = 7 * app.panel_height
app.width = 4 * app.panel_width
app.both_rounds = function() {
return app.data.candidates.first_round.concat(app.data.candidates.second_round)
}
app.min = function() {
return d3.min(app.both_rounds().concat(app.data.not_voted).map(function(d) { return d.original_votes }))
}
app.max = function() {
return d3.max(app.both_rounds().concat(app.data.not_voted).map(function(d) { return d.original_votes }))
}
app.votes_including_not_voted = function() {
return app.valid_votes().concat([app.data.not_voted.original_votes])
}
app.valid_votes = function() {
return app.both_rounds().map(function(d) { return d.final_votes })
}
app.r = d3.scale.linear().domain([app.min(),app.max()]).range([10,app.panel_height])
app.pie = d3.layout.pie().sort(null)
app.percent = d3.format(".1%")
app.humanize = d3.format(",r")
app.isWinner = function(d) { return d.final_votes >= d3.max(app.valid_votes()) }
app.isCandidate = function(d) { return d.id == 'ZEM' || d.id == 'SCH' }
app.findCandidate = function(id) {
return app.both_rounds().filter(function(e) { return e.id == id})[0]
}
app.votes_difference = function(d,i) {
var max = d3.max( app.data.candidates.second_round.map(function(d) { return d.final_votes} ) )
, min = d3.min( app.data.candidates.second_round.map(function(d) { return d.final_votes} ) )
return max - min
}
app.votes_in_first_round = function() {
return app.data.candidates.first_round.map(function(d) { return d.original_votes })
}
app.votes_in_second_round = function() {
return d3.sum( app.data.candidates.second_round.map(function(d) { return d.final_votes} ) )
}
app.append_volume_label = function(target, klass) {
return target.append("svg")
.attr("width", 12)
.attr("height", 7)
.classed(klass, true)
.append("polygon")
.attr("points", "1,6 11,6 11,1")
}
app.overlay_content = function(url, title) {
if (app.locale == 'en') {
return '<p><i class="icon-link"></i>Link to your scenario:</p>' +
'<p class="url"><strong><a href="'+url+'" target="_blank">'+url+'</a></strong></p>' +
'<p></p>' +
'<a href="https://twitter.com/share" class="twitter-share-button" data-url="'+url+'" data-text="'+title+'" data-hashtags="volby2013" data-count="none" data-dnt="true" target="_blank">Tweet</a>' +
'<a class="open" href="'+url+'" target="_blank"><i class="icon-share-alt"></i>Open</a>' +
'<a class="close" href="#"><i class="icon-remove-sign"></i>Close</a>'
} else {
return '<p><i class="icon-link"></i>Odkaz na váš scénář:</p>' +
'<p class="url"><strong><a href="'+url+'" target="_blank">'+url+'</a></strong></p>' +
'<p></p>' +
'<a href="https://twitter.com/share" class="twitter-share-button" data-url="'+url+'" data-text="'+title+'" data-hashtags="volby2013" data-count="none" data-dnt="true" target="_blank">Tweet</a>' +
'<a class="open" href="'+url+'" target="_blank"><i class="icon-share-alt"></i>Otevřít</a>' +
'<a class="close" href="#"><i class="icon-remove-sign"></i>Zavřít</a>'
}
}
app.handlers = {
participation : function(d,v) {
var v = parseInt(v)
d.id == 'NOT_VOTED' ? app.data.not_voted.new_voters_percent = v
: app.findCandidate(d.id).new_voters_percent = v
app.handlers.__update_data()
return app.update()
},
split : function(d,v) {
var v = parseInt(v)
d.id == 'NOT_VOTED' ? app.data.not_voted.new_voters_split = v
: app.findCandidate(d.id).new_voters_split = v
app.handlers.__update_data()
return app.update()
},
__update_data : function() {
var zem = app.findCandidate('ZEM')
, sch = app.findCandidate('SCH')
, not = app.data.not_voted
, participation_ZEM = Math.floor(zem.original_votes * zem.new_voters_percent/100)
, participation_SCH = Math.floor(sch.original_votes * sch.new_voters_percent/100)
, new_votes_ZEM = Math.floor(participation_ZEM * (100-zem.new_voters_split)/100) +
Math.floor(participation_SCH * (100-sch.new_voters_split)/100)
, new_votes_SCH = Math.floor(participation_ZEM * zem.new_voters_split/100) +
Math.floor(participation_SCH * sch.new_voters_split/100)
zem.final_votes = new_votes_ZEM
sch.final_votes = new_votes_SCH
not.final_votes = Math.floor(not.original_votes * not.new_voters_percent/100)
app.data.candidates.first_round.concat([app.data.not_voted]).forEach(function(c) {
// var percent = c.id == 'NOT_VOTED' ? 100-c.new_voters_percent : c.new_voters_percent
var percent = c.new_voters_percent
, split = c.new_voters_split
, new_votes = Math.floor(c.original_votes * percent/100)
, new_votes_ZEM = Math.floor(new_votes * (100-split)/100)
, new_votes_SCH = Math.floor(new_votes * split/100)
zem.final_votes += new_votes_ZEM
sch.final_votes += new_votes_SCH
});
},
__save_data : function() {
var overlay = d3.select("#overlay")
overlay.style("opacity", 0)
.style("display", "block")
.transition()
.duration(750)
.style("opacity", 0.8)
d3.select(window)
.on("keyup", function() {
if (this.event.keyCode == 27) {
d3.select("#app_save_data_popover").style("display", "none")
overlay.style("display", "none")
}
})
d3.xhr("/api")
.post(JSON.stringify(app.data))
.on('error', function(response) {
// console.log(response)
var error_message = ( app.locale == 'en' ) ?
'<p class="error"><i class="icon-warning-sign"></i>We\'re sorry, but there has been an error when saving your scenario...</p>' :
'<p class="error"><i class="icon-warning-sign"></i>Litujeme, při ukládání vašeho scénáře došlo k chybě...</p>'
d3.select("#app_save_data_popover")
.style("opacity", 0)
.style("display", "block")
.html(error_message)
.transition()
.duration(150)
.style("opacity", 1)
})
.response(function(response) {
d3.select("#app_save_data_popover").style("display", "none")
var id = JSON.parse(response.responseText).id
, url = app.origin_url + "/?id=" + id
, title = 'Můj scénář voleb:'
, content = app.overlay_content(url, title);
d3.select("#app_save_data_popover")
.style("opacity", 0)
.style("display", "block")
.html(content)
.transition()
.duration(150)
.style("opacity", 1)
d3.select("#app_save_data_popover a.close")
.on("click", function() {
d3.event.preventDefault()
d3.select("#app_save_data_popover").style("display", "none")
overlay.style("display", "none")
return this
})
twttr.widgets.load()
return this
})
},
__load_data : function(uri) {
var url = (window.location.protocol == 'file:' || window.location.origin == 'http://bl.ocks.org' ) ? uri : "/" + uri
d3.json(url + '?t=' + (new Date).getTime(), function(error, json) {
if (error) {
var error_message = ( app.locale == 'en' ) ?
'<p class="error"><i class="icon-warning-sign"></i>We\'re sorry, the requested data cannot be loaded...</p>' :
'<p class="error"><i class="icon-warning-sign"></i>Litujeme, ale požadovaná data nelze načíst...</p>'
d3.select("#app_save_data_popover")
.style("opacity", 0)
.style("display", "block")
.html(error_message)
.transition()
.duration(150)
.style("opacity", 1)
throw(error)
}
d3.select("#app_save_data_popover").style("display", "none")
app.data = json
app.handlers.__update_data()
app.update()
})
}
}
app.update = function() {
var update_splits = function(selector, index) {
chart.selectAll(selector)
.data(app.data.candidates.first_round)
.style("display", function(d,i) { return d.new_voters_percent > 0 ? "" : "none" })
.transition()
.duration(750)
.attrTween("d", function(d,i) {
var zem_values = d.new_voters_percent*((100-d.new_voters_split)/100)
, sch_values = d.new_voters_percent*(d.new_voters_split/100)
, rest = 100 - zem_values - sch_values
, values = [zem_values, sch_values, rest]
, pie = app.pie(values)
, outerRadius = app.r(d.final_votes)
, innerRadius = Math.round(app.r(d.final_votes)-app.r(d.final_votes)*0.8)
, arc = d3.svg.arc().outerRadius(outerRadius).innerRadius(innerRadius)
// console.log( index, d.id, values, d, pie[index], pie, this._current_value )
this._current_value = this._current_value || app.pie([0,100])[0]
var interpolate = d3.interpolate(this._current_value, pie[index])
this._current_value = interpolate(0)
return function(t) { return arc( interpolate(t) ) }
})
};
chart.selectAll("g.candidate.second_round")
.data(app.data.candidates.second_round)
.classed("winner", function(d) { return app.isWinner(d)})
chart.selectAll(".first_round circle.chart")
.transition()
.attr("r", function(d,i) { return app.r(d.final_votes)})
update_splits('.first_round path.arc.zem', 0)
update_splits('.first_round path.arc.sch', 1)
chart.selectAll(".second_round circle.chart")
.data(app.data.candidates.second_round)
.transition()
.attr("cy", function(d,i) { return app.r(d3.max(app.votes_including_not_voted())) + 50 })
.attr("r", function(d,i) { return app.r(d.final_votes) })
chart.selectAll(".second_round circle.outline")
.data(app.data.candidates.second_round)
.style("stroke-width", 0)
.transition()
.attr("cy", function(d,i) { return app.r(d3.max(app.votes_including_not_voted())) + 50 })
.attr("r", function(d,i) { return app.r(d.final_votes) + 6 } )
.duration(750)
.style("stroke-width", 6)
chart.selectAll(".second_round text.difference")
.data(app.data.candidates.second_round)
.style("opacity", 0)
.style("display", function(d,i) { return app.isWinner(d) ? "" : "none" })
.text(function(d) { return "+" + app.humanize(app.votes_difference()) })
.transition()
.duration(500)
.style("opacity", 1)
chart.selectAll(".second_round text.percent")
.data(app.data.candidates.second_round)
.text(function(d) { return app.percent( d.final_votes / app.votes_in_second_round() ) })
.transition()
.attr("y", function(d,i) { return app.r(d3.max(app.votes_including_not_voted())) + 55 })
chart.selectAll(".candidate.second_round text.info")
.data(app.data.candidates.second_round)
.text(function(d) { return app.humanize(d.final_votes) })
chart.selectAll(".not_voted circle.chart")
.datum(app.data.not_voted)
.transition()
.attr("cy", function(d,i) { return app.r(d3.max(app.votes_including_not_voted())) + 50 })
.attr("r", function(d,i) { return app.r(d.original_votes - d.final_votes)})
chart.selectAll(".not_voted text.info")
.datum(app.data.not_voted)
.text(function(d) { return app.humanize(d.original_votes - d.final_votes) })
d3.selectAll("#sliders .participation input")
.property("value", function(d,i) {
var v = app.both_rounds().filter(function(e) { return e.id == d.id })[0] || app.data.not_voted
return v.new_voters_percent
})
d3.selectAll("#sliders .split input")
.property("value", function(d,i) {
var v = app.both_rounds().filter(function(e) { return e.id == d.id })[0] || app.data.not_voted
return v.new_voters_split
})
}
// Handle data updates
//
app.handlers.__update_data()
// Setup SVG
//
var chart = d3.select("#chart")
.append("svg")
.attr("width", app.width)
.attr("height", app.height)
.append("g")
.attr("transform", "translate(" + app.panel_width/2 + "," + app.panel_height/2 + ")");
// Panels (first round)
//
var panel_first_round = chart
.selectAll("g.candidate.first_round")
.data(app.data.candidates.first_round);
var g_first_round = panel_first_round.enter()
.append("g")
.classed("candidate first_round", true)
.attr("name", function(d) { return d.name})
.attr("transform", function(d, i) {
var x = 0, y = (i * app.panel_height)
return "translate(" + x + "," + y + ")"
})
g_first_round.append("rect")
.attr("x", -app.panel_width/2)
.attr("y", -app.panel_height/2)
.attr("width", app.panel_width)
.attr("height", app.panel_height)
g_first_round.append("circle")
.classed("chart", true)
.attr("cx", function(d,i) {
return - app.panel_width/2
+ app.r(d.final_votes)
+ (app.r(d3.max(app.votes_in_first_round())) - app.r(d.final_votes))
+ 10
})
.attr("cy", 0)
.attr("r", function(d,i) { return app.r(d.final_votes)})
g_first_round.append("text")
.attr("class", "name")
.attr("x", app.panel_width/2 - 10)
.attr("y", -app.panel_height/2 + 22)
.text(function(d) { return d.name })
g_first_round.append("text")
.attr("class", "info")
.attr("x", app.panel_width/2 - 10)
.attr("y", -app.panel_height/2 + 36)
.text(function(d) { return app.humanize(d.final_votes) })
new Array('zem', 'sch').forEach(function(id) {
g_first_round.append("path")
.classed("arc", true)
.classed("zem", function() { return id == 'zem' })
.classed("sch", function() { return id == 'sch' })
.attr("transform", function(d, i) {
var x = - app.panel_width/2
+ app.r(d.final_votes)
+ (app.r(d3.max(app.votes_in_first_round())) - app.r(d.final_votes))
+ 10
, y = 0
return "translate(" + x + "," + y + ")"
})
})
// Panels (second round)
//
var panel_second_round = chart
.selectAll("g.candidate.second_round")
.data(app.data.candidates.second_round);
var g_second_round = panel_second_round.enter()
.append("g")
.classed("candidate second_round", true)
.classed("winner", function(d) { return app.isWinner(d)})
.classed("zem", function(d,i) { return d.id == 'ZEM' })
.classed("sch", function(d,i) { return d.id == 'SCH' })
.attr("name", function(d) { return d.name })
.attr("transform", function(d, i) {
var x = (i * app.panel_width + app.panel_width), y = 0
return "translate(" + x + "," + y + ")"
})
g_second_round.append("rect")
.attr("x", -app.panel_width/2)
.attr("y", -app.panel_height/2)
.attr("width", app.panel_width)
.attr("height", 7 * app.panel_height)
g_second_round.append("circle")
.classed("outline", true)
.attr("cx", 0)
.attr("cy", function(d,i) { return app.r(d3.max(app.votes_including_not_voted())) + 50 })
.attr("r", function(d,i) { return app.r(d.final_votes) + 8 })
g_second_round.append("circle")
.classed("chart", true)
.attr("cx", 0)
.attr("cy", function(d,i) { return app.r(d3.max(app.votes_including_not_voted())) + 50 })
.attr("r", function(d,i) { return app.r(d.final_votes) })
g_second_round.append("text")
.attr("class", "percent")
.attr("x", 2)
.attr("y", function(d,i) { return app.r(d3.max(app.votes_including_not_voted())) + 55 })
.text(function(d) { return app.percent( d.final_votes / app.votes_in_second_round() ) })
g_second_round.append("text")
.attr("class", "name")
.attr("x", 0)
.attr("y", -10)
.text(function(d) { return d.name })
g_second_round.append("text")
.attr("class", "info")
.attr("x", 0)
.attr("y", 10)
.text(function(d) { return app.humanize(d.final_votes) })
g_second_round.append("text")
.attr("class", "difference")
// .style("display", function(d,i) { return app.isWinner(d) ? "" : "none" })
.style("display", "none")
.attr("x", 0)
.attr("y", 25)
.text(function(d) { return "+" + app.humanize(app.votes_difference()) })
// Panel not voted
//
var panel_not_voted = chart
.selectAll("g.candidate.not_voted")
.data([app.data.not_voted]);
var g_not_voted = panel_not_voted.enter()
.append("g")
.classed("candidate not_voted", true)
.attr("name", "Not Voted")
.attr("transform", function(d, i) {
var x = ((2+i) * app.panel_width + app.panel_width), y = 0
return "translate(" + x + "," + y + ")"
})
g_not_voted.append("rect")
.attr("x", -app.panel_width/2)
.attr("y", -app.panel_height/2)
.attr("width", app.panel_width)
.attr("height", 7 * app.panel_height)
g_not_voted.append("circle")
.classed("chart", true)
.attr("cx", 0)
.attr("cy", function(d,i) {
return app.r(d3.max(app.votes_including_not_voted())) + 50
})
.attr("r", function(d,i) { return app.r(d.original_votes - d.final_votes)})
g_not_voted.append("text")
.attr("class", "name")
.attr("x", 0)
.attr("y", -10)
.text(function(d) { return app.locale == 'en' ? "Does not vote" : "Nejde k volbám" })
g_not_voted.append("text")
.attr("class", "info")
.attr("x", 0)
.attr("y", 10)
// Sliders
//
app.data.candidates.first_round.forEach(function(d,i) {
var div = d3.select("#sliders").append("div")
.datum(d)
.classed("sliders", true)
.classed("participation", true)
.style("width", app.panel_width+'px')
.style("top", Math.floor(i * app.panel_height + app.panel_height/2) + 7 + 'px')
.style("left", Math.floor(app.panel_width-app.panel_width*0.35) - 40 + 'px')
app.append_volume_label(div, 'min')
div.append("input")
.attr("type", "range")
.attr("id", function() { return d.id + "-participation-slider"})
.classed("candidate-slider", true)
.style("width", app.panel_width*0.35+'px')
.property("value", d.new_voters_percent)
.on("change", function() { return app.handlers.participation(d, this.value) })
app.append_volume_label(div, 'max')
})
app.data.candidates.first_round.forEach(function(d,i) {
var div = d3.select("#sliders").append("div")
.datum(d)
.classed("sliders", true)
.classed("split", true)
.style("width", app.panel_width+'px')
.style("top", Math.floor(i * app.panel_height + app.panel_height/2) + 23 + 'px')
.style("left", Math.floor(app.panel_width-app.panel_width*0.35) - 40 + 'px')
div.append("label")
.attr("class", "zem")
.text("Z")
div.append("input")
.attr("type", "range")
.attr("id", function() { return d.id + "-split-slider"})
.classed("candidate-slider", true)
.style("width", app.panel_width*0.35+'px')
.property("value", d.new_voters_split)
.on("change", function() { return app.handlers.split(d, this.value) })
div.append("label")
.attr("class", "sch")
.text("S")
})
app.data.candidates.second_round.forEach(function(d,i) {
var div = d3.select("#sliders").append("div")
.datum(d)
.classed("sliders", true)
.classed("participation", true)
.style("width", app.panel_width+'px')
.style("top", (app.panel_height*7)*0.5 + 'px')
.style("left", app.panel_width + i*app.panel_width + app.panel_width/2 - app.panel_width*0.35/2 - 10 + 'px')
app.append_volume_label(div, 'min')
div.append("input")
.attr("type", "range")
.attr("id", function() { return d.id + "-participation-slider"})
.classed("candidate-slider", true)
.style("width", app.panel_width*0.35+'px')
.property("value", d.new_voters_percent)
.on("change", function() { return app.handlers.participation(d, this.value) })
app.append_volume_label(div, 'max')
var div = d3.select("#sliders").append("div")
.datum(d)
.classed("sliders", true)
.classed("split", true)
.style("width", app.panel_width+'px')
.style("top", (app.panel_height*7)*0.5 + 20 + 'px')
.style("left", app.panel_width + i*app.panel_width + app.panel_width/2 - app.panel_width*0.35/2 - 10 + 'px')
div.append("label")
.attr("class", "zem")
.text("Z")
div.append("input")
.attr("type", "range")
.attr("id", function() { return d.id + "-split-slider"})
.classed("candidate-slider", true)
.style("width", app.panel_width*0.35+'px')
.property("value", d.new_voters_split)
.on("change", function() { return app.handlers.split(d, this.value) })
div.append("label")
.attr("class", "sch")
.text("S")
})
new Array(app.data.not_voted).forEach(function(d,i) {
var div = d3.select("#sliders").append("div")
.datum(d)
.classed("sliders", true)
.classed("participation", true)
.style("width", app.panel_width+'px')
.style("top", (app.panel_height*7)*0.5 + 'px')
.style("left", app.panel_width*3 + i*app.panel_width + app.panel_width/2 - app.panel_width*0.35/2 - 10 + 'px')
app.append_volume_label(div, 'min')
div.append("input")
.attr("type", "range")
.attr("id", function() { return d.id + "-participation-slider"})
.classed("candidate-slider", true)
.style("width", app.panel_width*0.35+'px')
.property("value", d.new_voters_percent)
.on("change", function() { return app.handlers.participation(d, this.value) })
app.append_volume_label(div, 'max')
var div = d3.select("#sliders").append("div")
.datum(d)
.classed("sliders", true)
.classed("split", true)
.style("width", app.panel_width+'px')
.style("top", (app.panel_height*7)*0.5 + 20 + 'px')
.style("left", app.panel_width*3 + i*app.panel_width + app.panel_width/2 - app.panel_width*0.35/2 - 10 + 'px')
div.append("label")
.attr("class", "zem")
.text("Z")
div.append("input")
.attr("type", "range")
.attr("id", function() { return d.id + "-split-slider"})
.classed("candidate-slider", true)
.style("width", app.panel_width*0.35+'px')
.property("value", d.new_voters_split)
.on("change", function() { return app.handlers.split(d, this.value) })
div.append("label")
.attr("class", "sch")
.text("S")
})
// Photos
//
var photos = d3.select("#photos").append("div")
photos.append("div")
.classed("zem", true)
.append("img")
.property("src", "zem.jpg")
photos.append("div")
.classed("sch", true)
.append("img")
.property("src", "sch.jpg")
// About
//
photos.append("div")
.classed("about", true)
.html(d3.select("#about-text").html())
app.update()
return app
});
return app
};
{
"candidates" : {
"first_round" : [
{"id" : "FIS", "name" : "Jan Fischer", "original_votes" : 841437, "final_votes" : 841437, "new_voters_percent" : 0, "new_voters_split" : 50},
{"id" : "DIE", "name" : "Jiří Dienstbier", "original_votes" : 829297, "final_votes" : 829297, "new_voters_percent" : 0, "new_voters_split" : 50},
{"id" : "FRA", "name" : "Vladimír Franz", "original_votes" : 351916, "final_votes" : 351916, "new_voters_percent" : 0, "new_voters_split" : 50},
{"id" : "ROI", "name" : "Zuzana Roithová", "original_votes" : 255045, "final_votes" : 255045, "new_voters_percent" : 0, "new_voters_split" : 50},
{"id" : "TAT", "name" : "Taťána Fischerová", "original_votes" : 166211, "final_votes" : 166211, "new_voters_percent" : 0, "new_voters_split" : 50},
{"id" : "SOB", "name" : "Přemysl Sobotka", "original_votes" : 126846, "final_votes" : 126846, "new_voters_percent" : 0, "new_voters_split" : 50},
{"id" : "BOB", "name" : "Jana Bobošíková", "original_votes" : 123171, "final_votes" : 123171, "new_voters_percent" : 0, "new_voters_split" : 50}
],
"second_round" : [
{"id" : "ZEM", "name" : "Miloš Zeman", "original_votes" : 1245848, "final_votes" : 1245848, "new_voters_percent" : 100, "new_voters_split" : 0},
{"id" : "SCH", "name" : "Karel Schwarzenberg", "original_votes" : 1204195, "final_votes" : 1204195, "new_voters_percent" : 100, "new_voters_split" : 100}
]
},
"not_voted" : {
"id" : "NOT_VOTED",
"name" : "Nejde k volbám",
"original_votes" : 3263856,
"final_votes" : 0,
"new_voters_percent" : 0,
"new_voters_split" : 50
}
}
<!DOCTYPE html>
<html>
<head>
<title>Rozhodněte prezidentské volby</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href='http://fonts.googleapis.com/css?family=Crete+Round&amp;subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300&amp;subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="application.css">
<style>
header
{ height: 130px; }
#sliders
{ top: 150px; }
#photos
{ top: 534px; }
</style>
</head>
<body>
<div id="application">
<header>
<section id="scenarios" class="clearfix">
<section>
<h2>Rozhodnou rady lídrů</h2>
<p>Část voličů nepřijde volit, zbytek dá zejména na rady svých neúspěšných kandidátů z prvního kola.</p>
<p class="buttons"><button onclick="app.handlers.__load_data('01.json')">Prohlédnout scénář</button></p>
</section>
<section>
<h2>Střet ideologií</h2>
<p>Druhé kolo se změní v nesmiřitelný boj levice s pravicí, nevyhranění voliči zůstanou spíše doma.</p>
<p class="buttons"><button onclick="app.handlers.__load_data('02.json')">Prohlédnout scénář</button></p>
</section>
<section>
<h2>Referendum o minulosti</h2>
<p>Vyostřená kampaň vyburcuje k volební účasti voliče z obou stran, kteří v prvním kole zůstali doma.</p>
<p class="buttons"><button onclick="app.handlers.__load_data('03.json')">Prohlédnout scénář</button></p>
</section>
<section>
<h2>Napište vlastní scénář</h2>
<p>Pomocí táhel u každého z kandidátů navolte jejich účast v druhém kole a rozdělení hlasů mezi oba finalisty.</p>
<p class="buttons">
<button onclick="app.handlers.__load_data('default.json')">Vynulovat</button>
<button id="app_save_data" onclick="app.handlers.__save_data()">Uložit</button>
<div id="app_save_data_popover"></div>
</p>
</section>
</section>
</header>
<div id="chart"></div>
<div id="sliders"></div>
<div id="photos"></div>
<div id="about-text" style="display: none">
<h2>O aplikaci</h2>
<p>Pomocí táhel u každého z kandidátů navolte jejich účast v druhém kole a rozdělení hlasů mezi oba finalisty.</p>
<p>Výsledek si můžete uložit jako odkaz.</p>
<p>Aplikace vyžaduje moderní prohlížeč: pokud nefunguje správně,
stáhněte si např. prohlížeč <a href="http://google.com/chrome" target="_blank">Google Chrome</a>.</p>
<p class="credentials">
Aplikaci vytvořili <a href="http://www.linkedin.com/in/vhyza" target="_blank">Vojtěch Hýža</a>,
<a href="http://www.linkedin.com/in/karelminarik" target="_blank">Karel Minařík</a>
a&nbsp;<a href="http://www.linkedin.com/in/josefslerka" target="_blank">Josef Šlerka</a> v&nbsp;roce 2013.
</p>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="application.js"></script>
<script>
var app = App({ domain: "idnes.cz", origin_url: "http://volby.idnes.cz/rozhodnete-volby.aspx" })
</script>
<script src="http://platform.twitter.com/widgets.js"></script>
<div id="overlay"></div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Czech Presidential Election 2013</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href='http://fonts.googleapis.com/css?family=Crete+Round&amp;subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300&amp;subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="application.css">
<style>
body
{ color: #222;
background: #f9f9f9;
margin: 2em; }
#application
{ border: 1px solid #efefef;
margin: 0 auto;
width: 980px;
border-radius: 2px;
box-shadow: #999 0 1px 6px;
position: relative; }
#sliders
{ left: -5px;
top: 220px; }
#colophon
{ color: #444;
text-align: center;
margin: 0 auto;
padding: 2em 0 2em 0;
width: 980px; }
#colophon a
{ color: #444; }
#colophon a.icon
{ padding: 2px;
border-radius: 2px; }
#colophon a.icon:hover
{ background: #fffa69 }
</style>
</head>
<body>
<div id="application">
<header>
<h1>Decide the election</h1>
<p>
The second round of Czech presidential election looks to be a close tie.
With this application, you can model different scenarios for the election outcome: where will the voters of candidates from the first round swing and how many new voters will come in the second round.
</p>
<section id="scenarios" class="clearfix">
<section>
<h2>The Leaders Will Decide</h2>
<p>Many voters from the first round won't come, the rest will respect the advice of their candidates.</p>
<p class="buttons"><button onclick="app.handlers.__load_data('01.json')">Load this scenario</button></p>
</section>
<section>
<h2>Ideology Clash</h2>
<p>The second round will be dominated by the clash between left and right, undecided voters will mostly stay at home.</p>
<p class="buttons"><button onclick="app.handlers.__load_data('02.json')">Load this scenario</button></p>
</section>
<section>
<h2>Vote About the Past</h2>
<p>Escalated campaigning will provoke voter participation from both sides.</p>
<p class="buttons"><button onclick="app.handlers.__load_data('03.json')">Load this scenario</button></p>
</section>
<section>
<h2>Write Your Own Scenario</h2>
<p>Use the sliders in each pane to configure voter participation in second round and the vote split.</p>
<p class="buttons">
<button onclick="app.handlers.__load_data('default.json')">Reset</button>
<button id="app_save_data" onclick="app.handlers.__save_data()">Save</button>
<div id="app_save_data_popover"></div>
</p>
</section>
</section>
</header>
<div id="chart"></div>
<div id="sliders"></div>
<div id="photos"></div>
<div id="about-text" style="display: none">
<h2>About</h2>
<p>Use the sliders in each pane to configure voter participation in second round and the vote split.
<p>You can save the result and share it.</p>
<p>The application requires a modern browser: if it does not seem to work correctly,
please download a new browser such as <a href="http://google.com/chrome">Google Chrome</a>.</p>
<p class="credentials">
The application was created by <a href="http://www.linkedin.com/in/vhyza">Vojtěch Hýža</a>,
<a href="http://www.linkedin.com/in/karelminarik">Karel Minařík</a>
and&nbsp;<a href="http://www.linkedin.com/in/josefslerka">Josef Šlerka</a> in 2013.
</p>
</div>
</div>
<div id="colophon">
<p><i class="icon-github"></i>&nbsp;<a class="icon" href="https://gist.github.com/4626240"><code>https://gist.github.com/4626240</code></a></p>
<p><i class="icon-comments"></i>&nbsp;<a class="icon" href="http://ephemera.karmi.cz/post/41370777074/decide-the-election-interactive-visualization"><code>http://ephemera.karmi.cz/post/41370777074/decide-the-election-interactive-visualization</code></a></p>
<p><i class="icon-external-link"></i>&nbsp;<a class="icon" href="http://zpravy.idnes.cz/prezidentske-volby-milose-zeman-karel-schwarzeneberg-ppn-/domaci.aspx?c=A130123_163354_domaci_jav"><code>http://zpravy.idnes.cz</code> <small>(in Czech)</small></a></p>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="application.js"></script>
<script>
var app = App({locale: 'en'})
</script>
<script src="http://platform.twitter.com/widgets.js"></script>
<div id="overlay"></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment