/* jshint strict:false */ /** * scrollVis - encapsulates * all the code for the visualization * using reusable charts pattern: * http://bost.ocks.org/mike/chart/ */ //Make diaper data global to hack bubble chart together var diaperData = []; var scrollVis = function() { // constants to define the size // and margins of the vis area. var width = 600; var height = 520; var margin = {top:10, left:20, bottom:40, right:30}; var hourFormat = d3.time.format('%H'); var times = ["1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a", "12p", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p", "12a"]; // Keep track of which visualization // we are on and which was the last // index activated. When user scrolls // quickly, we want to call all the // activate functions that they pass. var lastIndex = -1; var activeIndex = 0; //main svg used for the viz var svg = null; //d3 selection that will be used //for displaying visualizatoins var g = null; //set scales for graphs (will enter domain when data is processed) var xBarScale = d3.scale.linear() .range([0, width]); var yBarScale = d3.scale.linear() .range([height, 0]); var xAxisBar = d3.svg.axis() .scale(xBarScale) .orient("bottom") .ticks(24); //.tickFormat(d3.time.format("%H")); var yAxisBar = d3.svg.axis() .scale(yBarScale) .orient("left"); // The color transition used to show what hour // I should avoid when watching baby var diaperHourColorScale = d3.scale.linear() .domain([0,1.0]) .range(["#008080", "orange"]); // When scrolling to a new section // the activation function for that // section is called. var activateFunctions = []; // If a section has an update function // then it is called while scrolling // through the section with the current // progress through the section. var updateFunctions = []; /** * chart * * @param selection - the current d3 selection(s) * to draw the visualization in. For this * example, we will be drawing it in #vis */ var chart = function(selection) { selection.each(function(rawData) { svg = d3.select(this).selectAll("svg").data([diaperData]); svg.enter().append("svg").append("g"); svg.attr("width", width + margin.left + margin.right); svg.attr("height", height + margin.top + margin.bottom); //this group element will be used to conatin all other elements g = svg.select("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); //perform some data magic on data (call functions from above) var diaperData = getDiaperData(rawData); var totalDiapers = getTotalDiapers(diaperData); var diapersByHour = getDiapersByHour(diaperData); var diaperByHourMax = d3.max(diapersByHour, function(d) { return d.values; }); xBarScale.domain([1, 24]).nice(d3.time.day); yBarScale.domain([0, diaperByHourMax]); setupVis(diaperData, diapersByHour, totalDiapers); setupSections(); }); }; /** * setupVis - creates initial elements for all * sections of the visualization. * * @param diaperData = Data cleaned to only be diapers * @param diaperDatabyHour = Data grouped/aggregated by Hour */ setupVis = function(diaperData, diapersByHour, totalDiapers) { //Diapes title g.append("text") .attr("class", "title diapers-title") .attr("x", width / 2) .attr("y", height / 3) .attr("fill", "#008080") .text("Scroll Down"); g.append("text") .attr("class", "sub-title diapers-title") .attr("x", width / 2) .attr("y", (height / 3) + (height / 5) ) .attr("fill", "#008080") .text("↓"); g.selectAll(".diapers-title") .attr("opacity", 0); //Total Diaper Count Text g.append("circle") .attr("class", "diaperCountCircle") .attr("cx", width / 2) .attr("cy", height / 2) .attr("r", 0) .attr("fill", "#008080") .attr("opacity", 0); g.append("text") .attr("class", "title count-title highlight") .attr("x", width / 2) .attr("y", height / 2) .text(totalDiapers); g.append("text") .attr("class", "sub-title count-title highlight") .attr("x", width / 2) .attr("y", (height / 2) + (height / 7) ) .text("Diapers"); g.selectAll(".count-title") .attr("opacity", 0); //DiapersByHour BarChart var diapersbyHourArea = g.append("g") .attr("class", "diapersByHour"); var timeLabels = diapersbyHourArea.append("g") .attr("class", "x axis"); timeLabels.selectAll(".timeLabel") .data(times) .enter().append("text") .text(function(d) { return d; }) .attr("x", function(d, i) { return i * 24 + i * 2.2; }) .attr("y", 0) .style("text-anchor", "middle") .attr("transform", "translate(10, 535)") .attr("class", "timeLabel mono axis"); diapersbyHourArea.select(".x.axis").style("opacity", 0); diapersbyHourArea.append("g") .attr("class", "y axis") .call(yAxisBar); diapersbyHourArea.select(".y.axis").style("opacity", 0); var bars = diapersbyHourArea.selectAll(".bar").data(diapersByHour); bars.enter() .append("rect") .attr("class", "bar") .attr("x", function(d,i) { return xBarScale(i+1); }) .attr("y", function(d) { return yBarScale(d.values); }) .attr("width", width/25 ) .attr("height", function(d) { return height - yBarScale(d.values); }) .attr("fill", "#008080") .attr("opacity", 0); //Bar Text to show values at top of bar bars.enter() .append("text") .attr("class", "bartext") .attr("text-anchor", "middle") .attr("fill", "white") .attr("opacity", "0") .attr("x", function(d,i) { return xBarScale(i)+38; }) .attr("y", function(d) { console.log(yBarScale(d.values)); return yBarScale(d.values) + 18; }) .text(function(d){ return d.values; }); //BubbleChart var bubbleArea = g.append("g") .attr("class", "bubbleChart"); //Have to call this twice to work .. weird! Fix later bubble(bubbleArea, width, height); bubble(bubbleArea, width, height); bubbleArea.selectAll(".bubble") .transition() .duration(800) .attr("opacity", 0) .attr("fill", "#008080"); }; /** * setupSections - each section is activated * by a separate function. Here we associate * these functions to the sections based on * the section's index. * */ setupSections = function() { activateFunctions[0] = showTitle; activateFunctions[1] = doNothing; activateFunctions[2] = showDiaperTotal; activateFunctions[3] = showDiapersByHour; activateFunctions[4] = doNothing; activateFunctions[5] = sortDiapersByHour; activateFunctions[6] = showDiaperBubble; activateFunctions[7] = colorBubbleByChanger; activateFunctions[8] = sortBubbleByChanger; activateFunctions[9] = doNothing; // updateFunctions are called while // in a particular section to update // the scroll progress in that section. // Most sections do not need to be updated // for all scrolling and so are set to // no-op functions. for(var i = 0; i < 3; i++) { updateFunctions[i] = function() {}; } //e.g. to overide updateFunctions[2] = newHighlight(); }; /** * ------------------------------------------------------------ * ACTIVATE FUNCTIONS * * These will be called their * section is scrolled to. * * General pattern is to ensure * all content for the current section * is transitioned in, while hiding * the content for the previous section * as well as the next section (as the * user may be scrolling up or down). *-------------------------------------------------------------- */ /* **shows: title **hitdes diaper total */ function showTitle() { g.selectAll(".count-title") .transition() .duration(800) .attr("opacity", 0); g.selectAll(".diapers-title") .transition() .duration(800) .attr("opacity", 1.0); g.selectAll(".diaperCountCircle") .transition() .duration(800) .attr("opacity", 0) .attr("r", 0); } /* **hides: title **shows: total diapers count title **shows: total diaper circle **hides: Diapers by Hour */ function showDiaperTotal() { var waitTime = 6000; g.selectAll(".diapers-title") .transition() .duration(800) .attr("opacity", 0); g.selectAll(".count-title") .transition() .duration(waitTime) .attr("opacity", 1); g.selectAll(".diaperCountCircle") .transition() .duration(waitTime) .ease("elastic") .attr("opacity", 1) .attr("r", 250); g.selectAll(".bar") .transition() .duration(600) .attr("opacity", 0); g.select(".x.axis").style("opacity", 0); g.selectAll(".bartext") .transition() .duration(600) .attr("opacity", 0); } /* **hides: total diapers **shows: bar chart **hides: highlight diapers */ function showDiapersByHour() { g.selectAll(".count-title") .transition() .duration(800) .attr("opacity", 0); g.selectAll(".diaperCountCircle") .transition() .duration(800) .attr("opacity", 0) .attr("r", 0); g.selectAll(".bar") .attr("height", 0) .attr("y", height) .transition() .duration(1600) .ease("bounce") .attr("opacity", 1) .attr("y", function(d) { return yBarScale(d.values); }) .attr("height", function(d) { return height - yBarScale(d.values); }) .style("fill", "#008080"); g.selectAll(".bartext") .attr("height", 0) .attr("y", height) .transition() .duration(1600) .ease("bounce") .attr("opacity", 1) .attr("y", function(d) { return yBarScale(d.values) + 18; }) .attr("height", function(d) { return height - yBarScale(d.values); }); g.select(".x.axis").style("opacity", 1); } /* **highlights: Highlights the Bad Hours to Change Diapers */ function sortDiapersByHour() { g.selectAll(".bar") .attr("width", function(d) { return (d.values >= 4) ? width/30 : width/25; }) .transition() .duration(3000) .attr("opacity", 1) .ease("bounce") .attr("width", width/25) .style("fill", function(d) { return (d.values >= 11) ? "#d8b365" : "#008080"; }) .attr("width", width/25); g.select(".x.axis") .transition() .duration(1600) .style("opacity", 1); g.selectAll(".bubble") .transition() .duration(600) .attr("opacity", 0); } /* **hides: bar chart **shows: bubble chart **hides: color bubble chart */ function showDiaperBubble() { g.selectAll(".bar") .transition() .duration(800) .attr("opacity", 0); g.select(".x.axis").style("opacity", 0); display_all(); //Calls function in bubblechart.js g.selectAll(".bubble") .transition() .duration(800) .attr("opacity", 1); } function colorBubbleByChanger() { display_all(); color_by_caregiver(); } function sortBubbleByChanger() { toggle_view("caregiver"); //Calls function in bubblechart.js } function doNothing() { } /* ---------------------------------------------------------- DATA FORMATING FUNCTIONS ---------------------------------------------------------- */ /*Return only Diaper Data **Add hours format in */ function getDiaperData(rawData) { var diaperData = rawData.filter(function(d) { return d.Activity.includes('Diaper'); }); diaperData.forEach(function(d) { d.hour = (+hourFormat(new Date(d["Start Time"])))+1; d.caregiver_id = (d.Caregiver.includes('Cyrus') ? 0 : 1); d.caregiver = (d.Caregiver.includes('Cyrus') ? "Cyrus" : "Jen"); }); return diaperData; } /*** * Return the total Diaper Count */ function getTotalDiapers(diaperData) { return d3.nest() .rollup(function(v) {return v.length;}) .entries(diaperData); } /*Nest data to return total Diapers per Hour */ function getDiapersByHour(diaperData) { return d3.nest() .key(function(d) { return d.hour; }) .rollup(function(v) {return v.length; }) .entries(diaperData); } /** * activate chart * @param index - index of the activated section */ chart.activate = function(index) { activeIndex = index; var sign = (activeIndex - lastIndex) < 0 ? -1 : 1; var scrolledSections = d3.range(lastIndex + sign, activeIndex + sign, sign); scrolledSections.forEach(function(i) { activateFunctions[i](); }); lastIndex = activeIndex; }; //chart update chart.update = function(index, progress) { updateFunctions[index](progress); }; //return chart function return chart; }; function display(data) { //Create a new plot and display it var plot = scrollVis(); d3.select("#vis") .datum(data) .call(plot); //setup scroll functionality var scroll = scroller() .container(d3.select('#graphic')); //pass in .step selection as the steps scroll(d3.selectAll('.step')); //setup event handling scroll.on('active', function(index) { //highlight current step text d3.selectAll('.step') .style('opacity', function(d,i) { return i == index ? 1 : 0.1; }); //activate curent section plot.activate(index); }); } //load the data and display var data = d3.csv("changings.csv", display);