Last active
July 1, 2022 20:33
-
-
Save vi-enne/4b3887a2cc781c2652727fed0a82905d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<title>Cambi gruppo Senato XVIII</title> | |
<link rel="stylesheet" href="style/style.css" type="text/css" media="screen" /> | |
<link rel="stylesheet" type="text/css" href="//cloud.typography.com/7626174/696048/css/fonts.css" /> | |
<link href='https://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'> | |
</head> | |
<meta charset="utf-8"> | |
<style> | |
.node { | |
stroke-width: 1.5px; | |
} | |
</style> | |
<body> | |
<!-- Connecting with D3 library--> | |
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<div id="main-wrapper"> | |
<div id="sidebar"> | |
<div id="title">Visualizzazione dei cambi di gruppo parlamentare al Senato durante la XVIII legislatura</div> | |
<div id="current_time">2018-3-23</div> | |
<div id="speed"> | |
<div id="block_container"> | |
<button type="button" > | |
<div class="togglebutton slow current" data-val="slow">Lento</div> | |
</button> | |
<button type="button" > | |
<div class="togglebutton medium" data-val="medium">Medio</div> | |
</button> | |
<button type="button" > | |
<div class="togglebutton fast" data-val="fast">Veloce</div> | |
</button> | |
</div> | |
<div class="clr"></div> | |
</div> | |
<div id="note"></div> | |
</div> | |
<div id="chart"></div> | |
<div id="cite"> | |
Autore: <a href="https://twitter.com/vi__enne" target="_blank" rel="noopener">V. Nicoletta</a> - | |
Dati: <a href="https://parlamento18.openpolis.it/i-gruppi-in-parlamento/senato" target="_blank" rel="noopener">Openpolis</a> | |
(licenza | |
<a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener">CC-BY-NC-SA 4.0</a>) - | |
<a href="https://vi-enne.github.io/cambiCamera18/" target="_blank" rel="noopener"><i>Cambi Camera dei Deputati clicca qui</i></a> | |
<br> | |
Basato sulla visualizzazione | |
<a href="https://flowingdata.com/2015/12/15/a-day-in-the-life-of-americans/" target="_blank" rel="noopener">A Day in the Life of Americans</a> di | |
<a href="https://flowingdata.com/about" target="_blank" rel="noopener">Nathan Yau</a>, e sul codice di | |
<a href="https://gist.github.com/mohamedkhanafer/2f2ed4e2e7bff2c96481258675f88470" target="_blank" rel="noopener">Mohamed Khanafer</a> (licenza | |
<a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener">MIT</a>) | |
</div> | |
<div class="clr"></div> | |
</div> | |
<!-- Connecting with D3 library--> | |
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script> | |
var USER_SPEED = "slow"; | |
var width = 900, | |
height = 750, | |
padding = 1, | |
maxRadius = 3; | |
// color = d3.scale.category10(); | |
var sched_objs = [], | |
curr_time = 0; | |
var act_codes = [ | |
{"index": "9", "short": "Lega", "desc": "Lega"}, | |
{"index": "0", "short": "Fratelli d'Italia", "desc": "FdI"}, | |
{"index": "5", "short": "Autonomie", "desc": "Aut"}, | |
{"index": "11", "short": "CAL-PC-Idv", "desc": "CAL-PC-Idv"}, | |
{"index": "1", "short": "Misto", "desc": "Misto"}, | |
{"index": "6", "short": "PD", "desc": "PD"}, | |
{"index": "4", "short": "IPF-CD", "desc": "IpF"}, | |
{"index": "3", "short": "M5S", "desc": "M5S"}, | |
{"index": "2", "short": "Italia Viva-PSI", "desc": "IV"}, | |
{"index": "7", "short": "Eu-MAIE-CD", "desc": "EU-MAIE-CD"}, | |
{"index": "8", "short": "Forza Italia", "desc": "FI"}, | |
{"index": "10", "short": "Fuori Parlamento", "desc": "Out"}, | |
]; | |
var speeds = { "slow": 1000, "medium": 200, "fast": 20 }; | |
var time_notes = [ | |
{ "start_time": 1, "stop_time": 60, "note": "Inizio XVIII legislatura della Repubblica Italiana" }, | |
{ "start_time": 540, "stop_time": 640, "note": "Nasce Italia Viva-PSI" }, | |
{ "start_time": 1040, "stop_time": 1060, "note": "Breve formazione del gruppo Eu-MAIE-CD" }, | |
{ "start_time": 1064, "stop_time": 1164, "note": "Espulsione senatori M5S che non hanno votato la fiducia al governo Draghi" }, | |
{ "start_time": 1406, "stop_time": 1466, "note": "Si forma per un giorno il gruppo CAL-Idv" }, | |
{ "start_time": 1496, "stop_time": 1540, "note": "Nasce CAL-PC-Idv" }, | |
{ "start_time": 1555, "stop_time": 1650, "note": "Nasce Insieme Per il Futuro-CD" }, | |
]; | |
var notes_index = 0; | |
// Activity to put in center of circle arrangement | |
var center_act = "Out", | |
center_pt = { "x": 380, "y": 365 }; | |
// Coordinates for activities | |
var foci = {}; | |
act_codes.forEach(function(code, i) { | |
if (code.desc == center_act) { | |
foci[code.index] = center_pt; | |
} else { | |
var theta = 2 * Math.PI / (act_codes.length-1); | |
foci[code.index] = {x: 250 * Math.cos(i * theta)+380, y: 250 * Math.sin(i * theta)+365 }; | |
} | |
}); | |
// Start the SVG | |
var svg = d3.select("#chart").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
//Data loading | |
d3.csv('https://gist.githubusercontent.com/vi-enne/d2132a60b8580bf15dab94f787e48785/raw/2a7aacd82de111aea6ec0c1165c9b4ca09bbc682/senato18', function(data) { | |
data.forEach(function(d) { | |
var day_array = d.day.split(","); | |
var activities = []; | |
for (var i=0; i < day_array.length; i++) { | |
// Duration | |
if (i % 2 == 1) { | |
activities.push({'act': day_array[i-1], 'duration': +day_array[i]}); | |
} | |
} | |
sched_objs.push(activities); | |
}); | |
// Used for percentages by time | |
var act_counts = { "0": 0, "1": 0, "2": 0, "3": 0, "4": 0, "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "10": 0, "11": 0, "12": 0, "13": 0, "14": 0, "15": 0, "16": 0 }; | |
// A node for each person's schedule | |
var nodes = sched_objs.map(function(o,i) { | |
var act = o[0].act; | |
act_counts[act] += 1; | |
var init_x = foci[act].x + Math.random(); | |
var init_y = foci[act].y + Math.random(); | |
return { | |
act: act, | |
radius: 3, | |
x: init_x, | |
y: init_y, | |
color: color(act), | |
moves: 0, | |
next_move_time: o[0].duration, | |
sched: o, | |
} | |
}); | |
var force = d3.layout.force() | |
.nodes(nodes) | |
.size([width, height]) | |
// .links([]) | |
.gravity(0) | |
.charge(0) | |
.friction(.9) | |
.on("tick", tick) | |
.start(); | |
var circle = svg.selectAll("circle") | |
.data(nodes) | |
.enter().append("circle") | |
.attr("r", function(d) { return d.radius; }) | |
.style("fill", function(d) { return d.color; }); | |
// .call(force.drag); | |
// Activity labels | |
var label = svg.selectAll("text") | |
.data(act_codes) | |
.enter().append("text") | |
.attr("class", "actlabel") | |
.attr("x", function(d, i) { | |
if (d.desc == center_act) { | |
return center_pt.x; | |
} else { | |
var theta = 2 * Math.PI / (act_codes.length-1); | |
return 340 * Math.cos(i * theta)+380; | |
} | |
}) | |
.attr("y", function(d, i) { | |
if (d.desc == center_act) { | |
return center_pt.y; | |
} else { | |
var theta = 2 * Math.PI / (act_codes.length-1); | |
return 340 * Math.sin(i * theta)+365; | |
} | |
}); | |
label.append("tspan") | |
.attr("x", function() { return d3.select(this.parentNode).attr("x"); }) | |
// .attr("dy", "1.3em") | |
.attr("text-anchor", "middle") | |
.text(function(d) { | |
return d.short; | |
}); | |
label.append("tspan") | |
.attr("dy", "1.3em") | |
.attr("x", function() { return d3.select(this.parentNode).attr("x"); }) | |
.attr("text-anchor", "middle") | |
.attr("class", "actpct") | |
.text(function(d) { | |
return act_counts[d.index]; | |
}); | |
// Update nodes based on activity and duration | |
function timer() { | |
d3.range(nodes.length).map(function(i) { | |
var curr_node = nodes[i], | |
curr_moves = curr_node.moves; | |
// Time to go to next activity | |
if (curr_node.next_move_time == curr_time) { | |
if (curr_node.moves == curr_node.sched.length-1) { | |
curr_moves = 0; | |
} else { | |
curr_moves += 1; | |
} | |
// Subtract from current activity count | |
act_counts[curr_node.act] -= 1; | |
// Move on to next activity | |
curr_node.act = curr_node.sched[ curr_moves ].act; | |
// Add to new activity count | |
act_counts[curr_node.act] += 1; | |
curr_node.moves = curr_moves; | |
curr_node.cx = foci[curr_node.act].x; | |
curr_node.cy = foci[curr_node.act].y; | |
nodes[i].next_move_time += nodes[i].sched[ curr_node.moves ].duration; | |
} | |
}); | |
force.resume(); | |
curr_time += 1; | |
// Update percentages | |
label.selectAll("tspan.actpct") | |
.text(function(d) { | |
// return readablePercent(act_counts[d.index]); | |
return act_counts[d.index]; | |
}); | |
// Update time | |
var end_time = 1923 | |
var true_time = curr_time % end_time; | |
d3.select("#current_time").text(daysToDate(true_time)); | |
// Update notes | |
// var true_time = curr_time % 1440; | |
if (true_time == time_notes[notes_index].start_time) { | |
d3.select("#note") | |
.style("top", "0px") | |
.transition() | |
.duration(600) | |
.style("top", "20px") | |
.style("color", "#000000") | |
.text(time_notes[notes_index].note); | |
} | |
// Make note disappear at the end. | |
else if (true_time == time_notes[notes_index].stop_time) { | |
d3.select("#note").transition() | |
.duration(1000) | |
.style("top", "300px") | |
.style("color", "#ffffff"); | |
notes_index += 1; | |
if (notes_index == time_notes.length) { | |
notes_index = 0; | |
} | |
} | |
setTimeout(timer, speeds[USER_SPEED]); | |
} | |
setTimeout(timer, speeds[USER_SPEED]); | |
function tick(e) { | |
var k = 0.04 * e.alpha; | |
// Push nodes toward their designated focus. | |
nodes.forEach(function(o, i) { | |
var curr_act = o.act; | |
// Make sleep more sluggish moving. | |
if (curr_act == "0") { | |
var damper = 0.6; | |
} else { | |
var damper = 1; | |
} | |
o.color = color(curr_act); | |
o.y += (foci[curr_act].y - o.y) * k * damper; | |
o.x += (foci[curr_act].x - o.x) * k * damper; | |
}); | |
circle | |
.each(collide(.5)) | |
.style("fill", function(d) { return d.color; }) | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }); | |
} | |
// Resolve collisions between nodes. | |
function collide(alpha) { | |
var quadtree = d3.geom.quadtree(nodes); | |
return function(d) { | |
var r = d.radius + maxRadius + padding, | |
nx1 = d.x - r, | |
nx2 = d.x + r, | |
ny1 = d.y - r, | |
ny2 = d.y + r; | |
quadtree.visit(function(quad, x1, y1, x2, y2) { | |
if (quad.point && (quad.point !== d)) { | |
var x = d.x - quad.point.x, | |
y = d.y - quad.point.y, | |
l = Math.sqrt(x * x + y * y), | |
r = d.radius + quad.point.radius + (d.act !== quad.point.act) * padding; | |
if (l < r) { | |
l = (l - r) / l * alpha; | |
d.x -= x *= l; | |
d.y -= y *= l; | |
quad.point.x += x; | |
quad.point.y += y; | |
} | |
} | |
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; | |
}); | |
}; | |
} | |
// Speed toggle | |
d3.selectAll(".togglebutton") | |
.on("click", function() { | |
if (d3.select(this).attr("data-val") == "slow") { | |
d3.select(".slow").classed("current", true); | |
d3.select(".medium").classed("current", false); | |
d3.select(".fast").classed("current", false); | |
} else if (d3.select(this).attr("data-val") == "medium") { | |
d3.select(".slow").classed("current", false); | |
d3.select(".medium").classed("current", true); | |
d3.select(".fast").classed("current", false); | |
} | |
else { | |
d3.select(".slow").classed("current", false); | |
d3.select(".medium").classed("current", false); | |
d3.select(".fast").classed("current", true); | |
} | |
USER_SPEED = d3.select(this).attr("data-val"); | |
}); | |
}); // @end d3.tsv | |
function color(activity) { | |
var colorByActivity = { | |
"0": "#1a237e", | |
"1": "#78909c", | |
"2": "#ab47bc", | |
"3": "#ffeb3b", | |
"4": "#1b5e20", | |
"5": "#bcaaa4", | |
"6": "#f44336", | |
"7": "#80deea", | |
"8": "#0d47a1", | |
"9": "#303f9f", | |
"10": "#eceff1", | |
"11": "#ff5722", | |
} | |
return colorByActivity[activity]; | |
} | |
// Output readable percent based on count. | |
function readablePercent(n) { | |
var pct = 100 * n / 1000; | |
if (pct < 1 && pct > 0) { | |
pct = "<1%"; | |
} else { | |
pct = Math.round(pct) + "%"; | |
} | |
return pct; | |
} | |
// Day. Data is days from 2018-03-23. | |
function daysToDate(m) { | |
m = Math.min(m,1561); | |
var d = new Date("2018-03-23"); | |
d.setDate(d.getDate()+m); | |
var y = d.getFullYear(); | |
var da = d.getDate(); | |
var mo = d.getMonth() + 1; | |
return (y+'-'+mo+'-'+da); | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment