Skip to content

Instantly share code, notes, and snippets.

@mohamedkhanafer
Created February 7, 2020 21:19
Show Gist options
  • Select an option

  • Save mohamedkhanafer/fa9b9cbc82c20e6d71e46b646392d46c to your computer and use it in GitHub Desktop.

Select an option

Save mohamedkhanafer/fa9b9cbc82c20e6d71e46b646392d46c to your computer and use it in GitHub Desktop.
fresh block
license: mit
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<!-- Connecting with D3 library-->
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<!-- Google fonts Second Reference-->
<link href="https://fonts.googleapis.com/css?family=Source Sans Pro:100,200,300,400,500&display=swap" rel="stylesheet">
<!-- Google fonts Second Reference-->
<link href="https://fonts.googleapis.com/css?family=Roboto Mono:100,200,300,400,500&display=swap" rel="stylesheet">
<!-- Creating the headlines -->
<p id="h1">Time allocation of young moms </p>
<p id="h2"> Animation by Group A</p>
<style>
/*Defining text stylings*/
#h1 {
font-size: 39px;
margin: 0px 0px 2px 460px;
font-family: 'Source Sans Pro', sans-serif;
font-weight: 300;
font-variant: small-caps slashed-zero;
color: #4094aa;
}
#h2 {
font-size: 18px;
margin: 0px 0px 0px 595px;
font-family: 'Source Sans Pro', sans-serif;
font-weight: 100;
font-variant: small-caps slashed-zero;
color: #5cadc1;
}
/* #Reset & Basics (Inspired by E. Meyers)
================================================== */
#main-wrapper {
font-family:'Roboto Mono',sans-serif;
font-size: 15px;
width: 1200px;
}
#sidebar {
width: 270px;
float: left;
margin-right: 30px;
margin-top:50px;
padding-right: 20px;
border-right: 1px solid #eaeaea;
height: 800px;
text-align: center;
}
#current_time {
font-size: 25px;
color: #fe9001;
text-align: center;
margin: 140px 0 20px 25px;
}
#chart { width: 820px;
float: left;
margin-right: 30px;
margin-top:42px;
padding-right: 20px;
border-right: 1px solid #eaeaea;
border-top: 1px solid #eaeaea;
height: 800px;
text-align: center;
}
#speed {
font-family:'Roboto Mono',sans-serif;
font-size: 13px;
text-transform: uppercase;
margin: 4px 0 20px 25px;
}
#speed .togglebutton {
padding: 3px 3px;
text-align: center;
border: 1px solid #ccc;
cursor: pointer;
float: left;
width: 60px;
color:#7f7f7f;
}
#speed .togglebutton.slow {
border-right: none;
color:#7f7f7f;
}
#speed .togglebutton.fast {
border-left: none;
color:#7f7f7f;
}
#speed .togglebutton.current {
color: #fff;
background: #4094aa;
border: 1px solid #4094aa;
}
#note {
position: relative;
color: #fff;
padding-left: 5px;
padding-top: 110px;
font-family: 'Source Sans Pro', sans-serif;
font-style: italic;
font-weight: 400;
top: 50px;
font-size: 17px;
line-height: 1.4em;
}
#cite {
font-family:'Roboto Mono',sans-serif;
position: absolute;
border-top: 1px solid #eaeaea;
padding-top: 20px;
top: 750px;
font-size: 12px;
line-height: 1.4em;
width: 259px;
color: #93c9d6;
}
#expl {
position: absolute;
padding-top: 30px;
border-top: 1px solid #eaeaea;
top: 125px ;
font-size: 12px;
font-family: 'Roboto Mono', sans-serif;
line-height: 1.4em;
width: 259px;
color: #93c9d6
;
}
circle {
/* display: none;*/
}
.line {
/* fill: none;*/
/* stroke: #000;*/
stroke-width: 0.1px;
}
a {
color: #49a3ba;
text-decoration: none;
border-bottom: 1px solid #efefef;
}
a:hover {
border-bottom: 1px solid #000000;
}
.clr { clear: both; }
</style>
<body>
<div id="main-wrapper">
<div id="sidebar">
<div id="current_time"></div>
<div id="speed">
<div class="togglebutton slow current" data-val="slow">Slow</div>
<div class="togglebutton medium" data-val="medium">Fast</div>
<div class="togglebutton fast" data-val="fast">Pause</div>
<div class="clr"></div>
</div>
<div id="note" style="top: 20px; color: rgb(0, 0, 0);"></div>
<div id="cite">
Using <a href="https://twitter.com/beeonaposy/status/1215830967719485441">Caitlin Hudon's bargraph</a> we decided to complement it with an animation based on <a href="https://flowingdata.com/2016/08/23/make-a-moving-bubbles-chart-to-show-clustering-and-distributions/">Nathan Yau</a>'s tutorial.
Thanks for the great inspiration, Caitlin and Nathan!
</div>
<div id="expl">
How a woman spends her time changes significantly once having a baby. This visualization takes us throughout the first months of the parenting adventure.
</div>
</div>
<div id="chart"><svg width="220" height="50">
<div class="clr"></div>
<script>
var USER_SPEED = "slow";
var width = 800,
height = 800,
padding = 1,
maxRadius = 3;
// color = d3.scale.category10();
var sched_objs = [],
curr_minute = 0;
var act_codes = [
{"index": "1", "short": "SLEEPING", "desc": "Sleeping"},
{"index": "2", "short": "WORKING OR COMMUTING", "desc": "Work/Commute"},
{"index": "3", "short": "FREE TIME", "desc": "Free Time"},
{"index": "4", "short": "NURSING OR PUMPING", "desc": "Nursing / Pumping"},
{"index": "5", "short": "CARING FOR BABY", "desc": "Taking Care of Baby"}
];
/// here we are defining how fast are the 3 different options:
var speeds = { "slow": 70, "medium": 30, "fast": 10000 };
var time_notes = [
{ "start_minute": 1, "stop_minute": 60, "note": "Let the animation begin!" },
{ "start_minute": 90, "stop_minute": 210, "note": "First the routine is balanced between work, sleep, and free time." },
{ "start_minute": 240, "stop_minute": 340, "note": "The baby has arrived!" },
{ "start_minute": 370, "stop_minute": 450, "note": " Routine = Baby." },
{ "start_minute": 480, "stop_minute": 560, "note": "Less nursing, more 'Me-time'." },
{ "start_minute": 590, "stop_minute": 690, "note": "Mom is still on maternity leave." },
{ "start_minute": 720, "stop_minute": 820, "note": "Time to get back to work." },
{ "start_minute": 850, "stop_minute": 940, "note": "Older baby, a bit more free time." },
{ "start_minute": 960, "stop_minute": 1060, "note": "Back to a routine with a baby in the equation." },
{ "start_minute": 1100, "stop_minute": 1170, "note": "And last but not least..." },
{ "start_minute": 1200, "stop_minute": 1300, "note": "...Slowly reaching an equilibrium." },
{ "start_minute": 1370, "stop_minute": 1420, "note": "The end..." },
];
var notes_index = 0;
// Activity to put in center of circle arrangement
var center_act = "Sleeping",
center_pt = { "x": 380, "y": 345 };
// 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/mohamedkhanafer/c98be5b6981264a6d8054a0ae0436898/raw/e620fb6a09bd9a0e8f501d2de4ba93104622610c/gistfile1.txt', 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 minute
var act_counts = {"1": 0, "2": 0, "3": 0, "4": 0, "5": 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: 6,
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")
.style("fill", "#1f3c4a")
.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")
.style("fill", "#1f3c4a")
.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_minute) {
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_minute += 1;
// Update percentages
label.selectAll("tspan.actpct")
.text(function(d) {
return readablePercent(act_counts[d.index]);
});
// Update time
var true_minute = curr_minute % 1440;
d3.select("#current_time").text(minutesToTime(true_minute));
// Update notes
// var true_minute = curr_minute % 1440;
if (true_minute == time_notes[notes_index].start_minute) {
d3.select("#note")
.style("top", "0px")
.transition()
.duration(600)
.style("top", "20px")
.style("color", "#8c8c8c")
.text(time_notes[notes_index].note);
}
// Make note disappear at the end.
else if (true_minute == time_notes[notes_index].stop_minute) {
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 = 0.5;
}
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": "#e0d400",
"1": "#1c8af9",
"2": "#51BC05",
"3": "#FF7F00",
"4": "#DB32A4",
"5": "#00CDF8",
}
return colorByActivity[activity];
}
var d3Graph = d3.select('svg')
var dropShadowFilter = d3Graph.append('svg:filter')
.attr('id', 'drop-shadow')
.attr('filterUnits', "userSpaceOnUse")
.attr('width', '250%')
.attr('height', '250%');
dropShadowFilter.append('svg:feGaussianBlur')
.attr('in', 'SourceGraphic')
.attr('stdDeviation', 2)
.attr('result', 'blur-out');
dropShadowFilter.append('svg:feColorMatrix')
.attr('in', 'blur-out')
.attr('type', 'hueRotate')
.attr('values', 180)
.attr('result', 'color-out');
dropShadowFilter.append('svg:feOffset')
.attr('in', 'color-out')
.attr('dx', 3)
.attr('dy', 3)
.attr('result', 'the-shadow');
dropShadowFilter.append('svg:feBlend')
.attr('in', 'SourceGraphic')
.attr('in2', 'the-shadow')
.attr('mode', 'normal');
var simpleGradient = d3Graph.append('defs')
.append('radialGradient')
.attr('id', 'fake-shadow');
simpleGradient.append('stop')
.attr('offset', "80%")
.attr('stop-color', '#01AFAF');
simpleGradient.append('stop')
.attr('offset', "100%")
.attr('stop-color', "#01AFAF00");
// Output readable percent based on count.
function readablePercent(n) {
var pct = 100 * n / 100;
if (pct < 1 && pct > 0) {
pct = "<1%";
} else {
pct = Math.round(pct) + "%";
}
return pct;
}
// Minutes to time of day. Data is minutes from 4am.
function minutesToTime(m) {
var minutes = (m + 4*60) % 1440;
var hh = Math.floor(minutes / 60);
var ampm;
if (hh > 12) {
hh = hh - 12;
ampm = "pm";
} else if (hh == 12) {
ampm = "pm";
} else if (hh == 0) {
hh = 12;
ampm = "am";
} else {
ampm = "am";
}
var mm = minutes % 60;
if (mm < 10) {
mm = "0" + mm;
}
run=hh + ampm
if(run=="4am" || run=="5am" || run=="6am"|| run=="7am")
return "Pre-Pregnancy"
else if (run=="8am" || run=="9am" || run=="10am" ||run=="11am")
return "1 Month old Baby";
else if (run=="12pm" || run=="1pm" || run=="2pm" || run=="3pm")
return "2 Months old Baby";
else if (run=="4pm" || run=="5pm" || run=="6pm" || run=="7pm")
return "3 Months old Baby";
else if (run=="8pm" || run=="9pm" || run=="10pm" || run=="11pm")
return "4 Months old Baby";
else if (run=="12am" || run=="1am" || run=="2am" || run=="3am")
return "5-6 Months old Baby"
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment