We use cookies and other tracking technologies to improve your browsing experience on our website, to show you personalized content and targeted ads, to analyze our website traffic, and to understand where our visitors are coming from.
The Global Human Day
Observable
interactive
D3
TidyTuesday
How do people allocate their time in a day? Explore differences by country. Project based on resesarch provided by The Global Human Day study.
Summary
This project is based on data provided by a research study called The Global Human Day. The study aims to create a “global estimate of time use by all humans” and provides a snapshot of how humans allocate time across various activities.
There are a total of 20 activity classifications, or subcategories, that align with 8 parent categories (Somatic maintenance, Experience oriented, Food provision, Nonfood provision, Organization, Deliberate neural restructuring, Maintenance of surroundings, and Technosphere modification).
Information is available per country. This project explores how people in different countries prioritize their time across different activities.
categoryColor =function(category) {var colors = {"Food provision":"#48EB70","Nonfood provision":"#FF9D13","Technosphere modification":"#DCDCDC","Maintenance of surroundings":"#F95738","Somatic maintenance":"#EBCD49","Deliberate neural restructuring":"#4DEAFF","Organization":"#00BFB2","Experience oriented":"#7F2FFF" };return colors[category];}functioncolorHierarchy(hierarchy) {if(hierarchy.depth===0) { hierarchy.color='black'; } elseif(hierarchy.depth===1){ hierarchy.color=categoryColor(hierarchy.data.key); } else { hierarchy.color= hierarchy.parent.color; }if(hierarchy.children) { hierarchy.children.forEach( child =>colorHierarchy(child)) } }functiondecimalToHoursMinutes(decimal) {// Calculate the hours by rounding down the decimal numberconst hours =Math.floor(decimal);// Calculate the remaining minutes by multiplying the decimal part by 60const minutes =Math.round((decimal - hours) *60);let timeString ='';// Add hours to the output only if it's greater than 0if (hours >0) { timeString +=`${hours} hr${hours >1?'s':''}`; }// Add minutes to the output only if it's greater than 0if (minutes >0) {if (timeString) { timeString +=' '; } timeString +=`${minutes} min`; }// If neither hours nor minutes are greater than 0, return "0 min"if (!timeString) { timeString ="0 min"; }return timeString;}functioncustomTickFormat(value) {const hours =Math.floor(value);const minutes =Math.round((value - hours) *60);return`${hours}:${String(minutes).padStart(2,'0')}`;}functiongetCountryNameByISO3(code) {// Assuming tt_countries is an array of country objects// with 'country_name' and 'country_iso3' fieldsconst country = tt_countries.find((item) => item.country_iso3=== code);if (country) {return country.country_name; } else {return"Country not found";// You can customize the error message }}
{const width =550;const height =600;const margin = { top:20,right:5,bottom:30,left:10 };const svg = d3.create("svg").attr("viewBox", [0,0, width, height]).attr("style","max-width: 100%; height: auto; height: intrinsic; background-color:transparent");// Define your data (assuming you have a 'query' dataset)const data = queryconst categories = [...newSet(data.map(f => f.category))]// Create scales for x and y axesconst xScale = d3.scaleLinear().domain([0,30]).range([margin.left, width - margin.right]);const yScale = d3.scaleBand().domain(categories).range([margin.top, height - margin.bottom]).padding(0.1);// Add circles and assign unique IDs based on 'category'svg.selectAll("circle").data(data).enter().append("circle").attr("cx", d =>xScale(2)).attr("cy", d =>yScale(d.category)).attr("r",15).attr("fill", d =>categoryColor(d.category)).attr("id", d =>`${d.category}`) // Assign a unique ID based on 'category'.style("fill-opacity",1) // Set initial fill-opacity to 0.5 for all circles.on("mouseover",function (d) {const hoveredId =`${d.category}`; mutable hoveredCategory = hoveredId; svg.selectAll("circle").style("fill-opacity", circle => (circle.category=== hoveredId ?1:0.5)); svg.selectAll(".title").style("fill-opacity", circle => (circle.category=== hoveredId ?1:0.5)).style("font-weight", circle => (circle.category=== hoveredId ?'bold':'normal')); svg.selectAll(".value").style("fill-opacity", circle => (circle.category=== hoveredId ?1:0.5)).style("font-weight", circle => (circle.category=== hoveredId ?'bold':'normal')); }).on("mouseout",function () { mutable hoveredCategory =null;// Reset fill-opacity for all circles svg.selectAll("circle").style("fill-opacity",1); svg.selectAll(".title").style("fill-opacity",1).style('font-weight','normal'); svg.selectAll(".value").style("fill-opacity",1).style('font-weight','normal'); });svg.selectAll(".title").data(data).enter().append("text").attr("class","title").attr("x", d =>xScale(4)).attr("y", d =>yScale(d.category)).attr("font-size",20).attr("font-family","Oswald").attr("dy","0.35em").text(d => d.category);svg.selectAll(".value").data(data).enter().append("text").attr("class","value").attr("x", d =>xScale(25)).attr("y", d =>yScale(d.category)).attr("font-size",18).attr("font-family","Roboto Condensed").attr("text-anchor","end").attr("dy","0.35em").text(d =>decimalToHoursMinutes(d.country_stat)).sort((a, b) => d3.ascending(a.text, b.text));return svg.node();}
htl.html`<h2>How does ${selectedCountry} compare to other countries?</h2><p>Select a subcategory in the dropdown below or the treemap diagram above to see how other countries compare for a given activity.</p>`
How does Afghanistan compare to other countries?
Select a subcategory in the dropdown below or the treemap diagram above to see how other countries compare for a given activity.
viewof selectedSubcategory = Inputs.select(subcategories, {label:"Subcategory",value: clickedSubcategory})htl.html`<div style='margin-top:15px;margin-bottom:30px;'>People in <strong style='color:#1B98E0;'>${selectedCountry}</strong> spend on average <strong>${decimalToHoursMinutes(textHours)}</strong> for <strong>${selectedSubcategory}</strong>. This is <strong>${compareHours(textHours, textMedian)} than</strong> the <span style='border-bottom: 2px dashed;'>global median</span> by <strong/>${decimalToHoursMinutes(Math.abs(textHours-textMedian))}</strong>.</div>`
selectedSubcategory = "Food preparation"
People in Afghanistan spend on average 1 hr 8 min for Food preparation. This is higher than the global median by 20 min.
textHours = subset.filter(d => (d.Subcategory=== selectedSubcategory & d.country_iso3=== selectedIsoCode))[0].hoursPerDayCombinedtextMedian = d3.median(subset.filter(d => d.Subcategory=== selectedSubcategory), d=> d.hoursPerDayCombined)functioncompareHours(textHours, textMedian) {// Convert the input values to numbers (assuming they represent numbers)const hours =parseFloat(textHours);const median =parseFloat(textMedian);if (!isNaN(hours) &&!isNaN(median)) {if (hours > median) {return"higher"; } else {return"less"; } } else {// Handle the case where input values are not valid numbersreturn"Invalid input"; }}
functionvoronoiMap(hoveredCategory) {//const svg = d3.select(DOM.svg(width + margin.left + margin.right, height + margin.left + margin.right));// svg// .append("rect")// .attr("width", "100%")// .attr("height", "100%")// .style("fill", "white");const svg = d3.create("svg").attr("viewBox", [0,0, width + margin.left+ margin.right, height + margin.left+ margin.right]).attr("style","max-width: 100%; height: auto; height: intrinsic; background-color:transparent;");const voronoi = svg.append("g").attr("transform","translate("+ margin.left+","+ margin.top+")");const labels = svg.append("g").attr("transform","translate("+ margin.left+","+ margin.top+")");const hour_labels = svg.append("g").attr("transform","translate("+ margin.left+","+ margin.top+")");let seed =newMath.seedrandom(20);let voronoiTreeMap = d3.voronoiTreemap().prng(seed).clip(ellipse);voronoiTreeMap(category_hierarchy);colorHierarchy(category_hierarchy);let allNodes = category_hierarchy.descendants().sort((a, b) => b.depth- a.depth).map((d, i) =>Object.assign({}, d, {id: i}));let hoveredShape =null; voronoi.selectAll('path').data(allNodes).enter().append('path').attr('d', d =>"M"+ d.polygon.join("L") +"Z").style('fill', d => d.parent? d.parent.color: d.color).attr("stroke","white").attr("stroke-width",0).style('fill-opacity', d => d.depth===2?1:0).style('opacity', d => hoveredCategory ===null|| d.data.Category=== hoveredCategory ?1:0.5) // Use hoveredCategory variable.attr('pointer-events', d => d.depth===2?'all':'none').on('mouseenter',function (d) {// Select the current element and set its opacity to 1 d3.select(this).style('fill-opacity',1); d3.select(this).style("cursor","pointer");// Set opacity to 0.5 for all other elements voronoi.selectAll('path').filter(e => e.id!== d.id).style('opacity',0.4);// Other actions you want to perform on mouse enterlet label = labels.select(`.label-${d.id}`); label.attr('opacity',1);let hour_label = hour_labels.select(`.label-${d.id}`); hour_label.attr('opacity',1);}).on('mouseleave',function (d) {// Select the current element and restore its original fill opacity d3.select(this).style('fill-opacity', d => d.depth===2?1:0.5);// Restore opacity to 1 for all other elements voronoi.selectAll('path').filter(e => e.id!== d.id).style('opacity', d => d.depth===2?1:0);// Other actions you want to perform on mouse leavelet label = labels.select(`.label-${d.id}`); label.attr('opacity', d => d.data.hoursPerDayCombined>0.8?1:0);let hour_label = hour_labels.select(`.label-${d.id}`); hour_label.attr('opacity', d => d.data.hoursPerDayCombined>0.8?1:0);}).on('click', d => { mutable clickedSubcategory = d.data.key|| d.data.Subcategory;// Store the label's value in clickedCategory// Find the target div with class "beeswarm"const targetDiv =document.querySelector(".beeswarm");// Scroll to the target div smoothlyif (targetDiv) { targetDiv.scrollIntoView({behavior:'smooth' }); } else {console.log('Target div was not found.'); } }).transition().duration(500).attr("stroke-width", d =>7- d.depth*2.8).style('fill', d => d.color);//category labels labels.selectAll('text').data(allNodes.filter(d => d.depth===2 )).enter().append('text').attr('class', d =>`label-${d.id}`).attr('text-anchor','middle').attr("transform", d =>"translate("+[d.polygon.site.x, d.polygon.site.y+6]+")").text(d => d.data.key|| d.data.Subcategory) // .attr('opacity', d => d.data.key === hoveredShape | d.data.hoursPerDayCombined > 0.8 ? 1 : 0) .attr('opacity', d => (hoveredCategory !==null&& d.data.Category=== hoveredCategory) || (hoveredCategory ===null&& d.data.hoursPerDayCombined) >0.8?1:0).attr('cursor','pointer').attr('pointer-events','none').attr('fill', d => d.data.Category==='Experience oriented'?'white':'black').style('font-family','Oswald').style('text-transform','capitalize').style('font-size', d => d.data.hoursPerDayCombined>3?"30px": d.data.hoursPerDayCombined>1?"20px": d.data.hoursPerDayCombined>0.8?"16px":"14px");//hours combined labels hour_labels.selectAll('text').data(allNodes.filter(d => d.depth===2 )).enter().append('text').attr('class', d =>`label-${d.id}`).attr('text-anchor','middle').attr("transform", d =>"translate("+[d.polygon.site.x, d.polygon.site.y+25]+")").text(d =>decimalToHoursMinutes(d.data.hoursPerDayCombined)).attr('opacity', d => (hoveredCategory !==null&& d.data.Category=== hoveredCategory) || (hoveredCategory ===null&& d.data.hoursPerDayCombined) >0.8?1:0) .attr('cursor','pointer').attr('pointer-events','none').attr('fill', d => d.data.Category==='Experience oriented'?'white':'black').style('font-size', d=> d.data.hoursPerDayCombined>0.3?'14px':"12px").style('font-family','Roboto Condensed');return svg.node();}
voronoiMap = ƒ(hoveredCategory)
db = DuckDBClient.of({ tt_ghd_countries: tt_ghd_countries,tt_countries: tt_countries})
query = db.sql`SELECT Category as category, SUM(CASE WHEN country_group = 'USA' THEN hrs ELSE 0 END) AS country_stat, AVG(CASE WHEN country_group = 'World Avg' THEN hrs ELSE 0 END) AS global_statFROM ( SELECT country_iso3 ,CASE WHEN country_iso3 = ${selectedIsoCode} THEN 'USA' ELSE 'World Avg' END AS country_group, Category, SUM(hoursPerDayCombined) as hrs FROM tt_ghd_countries GROUP BY 1,2,3) aGROUP BY Category ORDER BY 2 desc;`