Built with blockbuilder.org
Created
February 7, 2020 21:19
-
-
Save mohamedkhanafer/fa9b9cbc82c20e6d71e46b646392d46c to your computer and use it in GitHub Desktop.
fresh block
This file contains hidden or 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
| license: mit |
This file contains hidden or 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> | |
| <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