//Width and height margin = { top: 50, right: 40, bottom: 20, left: 50 }, w = container.offsetWidth; h = 500; var parseTime = d3.timeParse('%Y'); //convert strings to dates var formatTime = d3.timeFormat('%Y'); //date format //Function for converting CSV values from strings to Dates and numbers var rowConverter = function(d, i, cols) { var row = { year: parseTime(d.year), //make new date object for each year }; for (var i = 1; i < cols.length; i++) { //loop for each growth type var col = cols[i]; row[cols[i]] = +d[cols[i]]; //convert from string to int } return row; }; //colors var colors = d3.scaleOrdinal(d3.schemeCategory10); //for back button toggle var viewState = 0 //bar transition duration var barTransition = 750 //define stacks var stack = d3.stack(); var transitionStack = d3.stack(); var thisStack = d3.stack(); //create svg var svg = d3.select('#container') .append('svg') .attr('width', w) .attr('height', h); //load data d3.csv('growth_data.csv', rowConverter, function(error, data) { if (error) throw error; dataset = data; //console.table(dataset); d3.csv('growth_data_lines.csv', rowConverter, function(error, gdpLineData) { if (error) throw error; gdpLineDataset = gdpLineData //console.log(gdpLineDataset); keys = dataset.columns.slice(1); stack.keys(keys) .offset(d3.stackOffsetDiverging) .order(d3.stackOrderInsideOut); //data, stacked series = stack(dataset); //console.log(series); drawGdp(dataset, series, keys); drawGdpLine(gdpLineDataset); drawBackbutton(dataset, series, keys); //LEGEND var legendVals = ["Personal Consumption", "Gross private domestic investment", "Net Trade", "Government consumption and gross investment"] //var legendVals = dataset.columns.slice(1); DYNAMIC LEGEND, not using because current column headers not pretty, but necessary for the way this dynamically loads the thisType files drawLegend(legendVals); }); }); function drawGdp(data, series, keys) { //scales xScale = d3.scaleBand() .domain(data.map(function(d) { return d.year })) .range([margin.left, w - margin.right]) .paddingInner(0.1) .paddingOuter(0.75); yScale = d3.scaleLinear() .domain([d3.min(series, stackMin), d3.max(series, stackMax)]) .range([h - margin.bottom, margin.top]) .nice(); //Define axes xAxis = d3.axisBottom() .scale(xScale) .tickValues(xScale.domain().filter(function(d, i) { return !(i % 10) })) .tickFormat(d3.timeFormat('%Y')) .tickSize(0); //Define right Y axis yAxisR = d3.axisRight() .scale(yScale) .ticks(8) .tickSizeOuter(0); //Define left Y axis yAxisL = d3.axisLeft() .scale(yScale) .ticks(8) .tickSizeOuter(0); //Define grey y axis lines yAxisGrid = d3.axisLeft() .scale(yScale) .ticks(8) .tickSizeOuter(0) .tickSizeInner(-w + margin.left + margin.right) .tickFormat(""); //group data rows var bars = svg.selectAll('#originalBars') .data(series) .enter() .append('g') .attr('id', 'originalBars') .style('fill', function(d, i) { return colors(i); }) .attr("class", function(d, i) { return d.key; }); //add rect for each data value var rects = bars.selectAll('rect') .data(function(d) { return d; }) .enter() .append('rect') .attr('x', function(d, i) { return xScale(d.data.year); }) .attr('y', function(d) { return yScale(d[1]); }) .attr('height', function(d) { return yScale(d[0]) - yScale(d[1]); }) .attr('width', xScale.bandwidth) .attr('id', 'indivBars') .attr('class', function(d, i) { return "bar-" + d3.select(this.parentNode).attr('class'); }) .style('cursor', 'pointer') .on("mousemove", function(d, i) { tooltipType = d3.select(this.parentNode).attr('class') //console.log(d.data[tooltipType]); tooltipDiv.transition() //.duration(200) .style("opacity", .9); tooltipDiv.html(tooltipType + "
" + formatTime(d.data.year) + ': ' + d.data[tooltipType] + '%') .style("left", (d3.event.pageX + 5) + "px") .style("top", (d3.event.pageY - 38) + "px"); }) .on("mouseout", function(d) { tooltipDiv.transition() .duration(500) .style("opacity", 0) }) rects.on('click', function(d, i) { thisType = d3.select(this.parentNode).attr('class') //console.log(thisType); viewState++ toggleBackButton() d3.csv('growth_data_' + thisType + '.csv', rowConverter, function(error, thisGdpData) { if (error) throw error; var thisDataset = thisGdpData; //console.log(thisDataset); //Generate a new data set with all-zero values, //except for this type's data for beginning of transition transitionDataset = []; for (var i = 0; i < data.length; i++) { transitionDataset[i] = { year: data[i].year, personal_consumption: 0, gross_private_domestic_inv: 0, net_trade: 0, gov_consumption_and_gross_inv: 0, [thisType]: data[i][thisType] //Overwrites the appropriate zero value above } } //console.log(transitionDataset); transitionStack.keys(keys) .offset(d3.stackOffsetDiverging) .order(d3.stackOrderAscending); var transitionSeries = transitionStack(transitionDataset); //console.log(transitionSeries); //remove gdp line d3.select('#line').transition().duration(barTransition / 2).style("opacity", 0); //update y scale yScale = d3.scaleLinear() .domain([d3.min(transitionSeries, stackMin) - 0.5, d3.max(transitionSeries, stackMax) + 0.5]) .range([h - margin.bottom, margin.top]) .nice(); //update y axes yAxisR = d3.axisRight() .scale(yScale) .ticks(8) .tickSizeOuter(0); yAxisL = d3.axisLeft() .scale(yScale) .ticks(8) .tickSizeOuter(0); yAxisGrid = d3.axisLeft() .scale(yScale) .ticks(8) .tickSizeOuter(0) .tickSizeInner(-w + margin.left + margin.right) .tickFormat(""); svg.select(".axis.yl") .transition().duration(barTransition) .call(yAxisL); svg.select(".axis.yr") .transition().duration(barTransition) .call(yAxisR); svg.select(".axis.ygrid") .transition().duration(barTransition) .call(yAxisGrid); svg.select(".axis.x") .transition().duration(barTransition) .select('.domain').attr('transform', 'translate(' + 0 + ',' + (yScale(0) - (h - margin.bottom)) + ')'); //transition bars keys.forEach(function(key, key_index) { var bars = svg.selectAll(".bar-" + key) .data(transitionSeries[key_index]) .transition().duration(barTransition) .attr("y", function(d) { return yScale(d[1]); }) .attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); }) .transition() .style('opacity', 0) }); thisKeys = thisDataset.columns.slice(1); thisStack.keys(thisKeys) .offset(d3.stackOffsetDiverging) .order(d3.stackOrderInsideOut); thisSeries = thisStack(thisDataset); //console.log(thisSeries); //group data rows var bars = svg.selectAll('#bars') .data(thisSeries) .enter() .append('g') .attr('id', 'bars') .style('fill', function(d, i) { return colors(i); }) .attr("class", function(d, i) { return d.key; }); //add rect for each data value var rects = bars.selectAll('rect') .data(function(d) { return d; }) .enter() .append('rect') .attr('x', function(d, i) { return xScale(d.data.year); }) .attr('y', function(d) { return yScale(d[1]); }) .attr('height', function(d) { return yScale(d[0]) - yScale(d[1]); }) .attr('width', xScale.bandwidth) .attr('id', 'indivBars') .attr('class', function(d, i) { return "thisBar-" + d3.select(this.parentNode).attr('class'); }) .on("mousemove", function(d, i) { tooltipType = d3.select(this.parentNode).attr('class') //console.log(d.data[tooltipType]); tooltipDiv.transition() //.duration(200) .style("opacity", .9); tooltipDiv.html(tooltipType + "
" + formatTime(d.data.year) + ': ' + d.data[tooltipType] + '%') .style("left", (d3.event.pageX + 5) + "px") .style("top", (d3.event.pageY - 38) + "px"); }) .on("mouseout", function(d) { tooltipDiv.transition() .duration(500) .style("opacity", 0) }) .style('opacity', 0) .transition().delay(barTransition) .style('opacity', 1); //net line for thisType drawThisLine(gdpLineDataset); //new legend d3.selectAll('.legend').classed('hidden', true); var legendVals = thisKeys //var legendVals = dataset.columns.slice(1); DYNAMIC LEGEND, not using because current column headers not pretty wLegend = w * 0.55; hLegend = h * 0.05; var legend = svg.selectAll('.thisLegend') .data(legendVals) .enter() .append('g') .attr("class", "thisLegend") .attr("transform", function(d, i) { { return "translate(0," + i * 20 + ")" } }) legend.append('rect') .attr("x", wLegend) .attr("y", hLegend + 30) .attr("width", 12) .attr("height", 12) .style("fill", function(d, i) { return colors(i) }) legend.append('text') .attr("x", wLegend + 20) .attr("y", hLegend + 42) //.attr("dy", ".35em") .text(function(d, i) { return d }) .attr("class", "textselected") }); }); //create axes svg.append('g') .attr('class', 'axis x') .attr('transform', 'translate(0,' + (h - margin.bottom) + ')') .call(xAxis) .style('font-size', 14) .select('.domain').attr('transform', 'translate(' + 0 + ',' + (yScale(0) - (h - margin.bottom)) + ')'); svg.append('g') .attr('class', 'axis yl') .attr('transform', 'translate(' + margin.left + ',0)') .call(yAxisL) .style('font-size', 14); svg.append('g') .attr('class', 'axis yr') .attr('transform', 'translate(' + (w - margin.right) + ',0)') .call(yAxisR) .style('font-size', 14); svg.append('g') .attr('class', 'axis ygrid') .attr('transform', 'translate(' + margin.left + ',0)') .call(yAxisGrid) .style('opacity', .2); // Define the div for the tooltip var tooltipDiv = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0); }; function drawGdpLine(data) { //define line line = d3.line() .x(function(d) { return xScale(d.year) + (xScale.bandwidth() / 2); }) .y(function(d) { return yScale(d.gdp); }) .curve(d3.curveMonotoneX); //create line svg.append("path") .datum(data) .attr("id", "line") .attr("d", line) .style('fill', 'none') .style('stroke', 'black') .style('stroke-width', 3); }; function drawThisLine(data) { //define line thisLine = d3.line() .x(function(d) { return xScale(d.year) + (xScale.bandwidth() / 2); }) .y(function(d) { return yScale(d[thisType]); }) .curve(d3.curveMonotoneX); //create line svg.append("path") .datum(data) .attr("id", 'thisLine') .attr("d", thisLine) .style('fill', 'none') .style('stroke', 'black') .style('stroke-width', 3) .style('opacity', 0) .transition().delay(barTransition).duration(500) .style('opacity', 1); }; function drawBackbutton(data, series, keys) { //Create back button var backButton = svg.append("g") .attr("id", "backButton") .style("opacity", 0) //Initially hidden .classed("unclickable", true) //Initially not clickable .attr("transform", "translate(" + xScale.range()[0] + "," + yScale.range()[1] + ")"); backButton.append("rect") .attr("x", 15) .attr("y", -30) .attr("rx", 5) .attr("rx", 5) .attr("width", 70) .attr("height", 30); backButton.append("text") .attr("x", 22) .attr("y", -10) .html("← Back"); //Define click behavior backButton.on("click", function() { viewState = 0 toggleBackButton(); //Set y scale back to original domain yScale = d3.scaleLinear() .domain([d3.min(series, stackMin), d3.max(series, stackMax)]) .range([h - margin.bottom, margin.top]) .nice(); //transition bars thisKeys.forEach(function(key, key_index) { var bars = svg.selectAll(".thisBar-" + key) .data(thisSeries[key_index]) .transition().duration(barTransition) .attr("y", function(d) { return yScale(d[1]); }) .attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); }) .style('opacity', 0); }); d3.selectAll('#bars').transition().duration(barTransition).remove(); //update y axes yAxisR = d3.axisRight() .scale(yScale) .ticks(8) .tickSizeOuter(0); yAxisL = d3.axisLeft() .scale(yScale) .ticks(8) .tickSizeOuter(0); yAxisGrid = d3.axisLeft() .scale(yScale) .ticks(8) .tickSizeOuter(0) .tickSizeInner(-w + margin.left + margin.right) .tickFormat(""); svg.select(".axis.yl") .transition().duration(barTransition) .call(yAxisL); svg.select(".axis.yr") .transition().duration(barTransition) .call(yAxisR); svg.select(".axis.ygrid") .transition().duration(barTransition) .call(yAxisGrid); svg.select(".axis.x") .transition().duration(barTransition) .select('.domain').attr('transform', 'translate(' + 0 + ',' + (yScale(0) - (h - margin.bottom)) + ')'); //transition bars keys.forEach(function(key, key_index) { var bars = svg.selectAll(".bar-" + key) .data(series[key_index]) .transition().duration(barTransition) .style('opacity', 1) .attr("y", function(d) { return yScale(d[1]); }) .attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); }) }); //original legend d3.selectAll('.thisLegend').remove(); d3.selectAll('.legend').classed('hidden', false); //remove thisLine d3.select('#thisLine').remove(); //original gdp line d3.select('#line').transition().duration(barTransition * 2).style("opacity", 1); }); }; function drawLegend(data) { wLegend = w * 0.55; hLegend = h * 0.05; var legend = svg.selectAll('.legend') .data(data) .enter() .append('g') .attr("class", "legend") .attr("transform", function(d, i) { { return "translate(0," + i * 20 + ")" } }); legend.append('rect') .attr("x", wLegend) .attr("y", hLegend + 30) .attr("width", 12) .attr("height", 12) .style("fill", function(d, i) { return colors(i) }) legend.append('text') .attr("x", wLegend + 20) .attr("y", hLegend + 42) //.attr("dy", ".35em") .text(function(d, i) { return d }) .attr("class", "textselected") } function toggleBackButton() { //Select the button var backButton = d3.select("#backButton"); //Decide whether to reveal or hide it if (viewState == 1) { //Reveal it //Set up dynamic button text var buttonText = "← Back"; //Set text backButton.select("text").html(buttonText); //Resize button depending on text width var rectWidth = Math.round(backButton.select("text").node().getBBox().width + 16); backButton.select("rect").attr("width", rectWidth); //Fade button in backButton.classed("unclickable", false) .transition() .duration(500) .style("opacity", 1); } else { //Hide it backButton.classed("unclickable", true) .transition() .duration(200) .style("opacity", 0); } }; function stackMin(serie) { return d3.min(serie, function(d) { return d[0]; }); }; function stackMax(serie) { return d3.max(serie, function(d) { return d[1]; }); }; // % label for the y axis svg.append("text") //.attr("x", margin.left / 2) //.attr("y", h / 2) .style("text-anchor", "middle") .text("%") .attr('class', 'axis text') .attr("transform", "translate(" + margin.left / 4 + "," + h / 2 + ") rotate(0)") .style('font-weight', 'bold') .style('pointer-events', 'none'); // source svg.append("text") .style("text-anchor", "middle") .attr('class', 'textselected') .text("Source: U.S. Bureau of Economic Analysis") .attr("transform", "translate(" + (w * 0.75) + "," + (h * 0.925) + ") rotate(0)") .style('pointer-events', 'none');