D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
alansmithy
Full window
Github gist
'Squeezed'
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Tax credit changes and the low income family</title> <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> <style type="text/css"> body, text{font-family:metric,sans-serif;} h2{font-weight:500;font-size:21px;margin-bottom:-10px;} body{background-color:#dedede} .axis text{font-size:11px;} .axis path{fill:none;stroke:none;} .axis line{stroke:#555;stroke-width:1px;} .y line{stroke-dasharray:2,2} .area{fill-opacity:0.8;} .source{font-size:11px;fill:#555;} .subtitle{font-weight:500;font-size:18px;} .value{font-weight:500;font-size:18px;} .origin line{stroke:#828778;stroke-dasharray:0,0} #container { width: 600px; margin-left: auto; margin-right: auto; margin-top: 30px; background-color: white; box-shadow: 2px 2px 4px 5px #ccc; } #intro{ padding-left:50px; padding-top:10px; padding-right:50px; } p{color:gray;} </style> </head> <body> <div id="container"> <div id="intro"> <h2>Squeezed: Tax credit changes and the low income family</h2> <p>The House of Lords recently blocked Chancellor George Osborne's plans to make cuts of £4.6bn to income subsidies for people on low incomes, known as tax credits. The government argues that other changes, including an increase in the minimum wage and raising the tax allowance will offset the cuts for most households. But those changes are set to take effect over the coming years, while the tax credit cuts will be imposed from April next year. The graphic below shows House of Commons Library research on how the planned changes would affect a low income family*.</p> </div> </div> <script type="text/javascript"> //Width, height, padding, colours var width = 600; var height = 250; var padding = {left:50,right:80,top:30,bottom:50}; var w = width-(padding.left+padding.right); var h = height-(padding.top+padding.bottom); var colour = ["#5999B3","#D9AB44","#0079A1"]; var labelColour = ["#666","#111","#fff"]; var labelPlacement = [[[280,-30],[280,5],[280,70]],[[280,-18],[280,5],[280,70]]]; var source="Source: House of Commons Library; *Single-earner couple or lone parent, with 2 children, working 35 hrs a week"; //should load this from a csv and create this structure var data=[]; var desc=["Without budget changes applied","With budget changes applied"]; //ignoring budget data[0] = [ { label: "child benefit", value: [ { x: "2016", y: 1794 }, { x: "2017", y: 1794 }, { x: "2018", y: 1815 }, { x: "2019", y: 1846 }, { x: "2020", y: 1880 }, { x: "2021", y: 1914 } ] }, { label: "tax credit", value: [ { x: "2016", y: 8504 }, { x: "2017", y: 8280 }, { x: "2018", y: 8223 }, { x: "2019", y: 8183 }, { x: "2020", y: 8131 }, { x: "2021", y: 8132 } ] }, { label: "net earnings", value: [ { x: "2016", y: 11401 }, { x: "2017", y: 11774 }, { x: "2018", y: 12116 }, { x: "2019", y: 12539 }, { x: "2020", y: 13026 }, { x: "2021", y: 13693 } ] } ] //data with budget applied data[1] = [ { label: "child benefit", value: [ { x: "2016", y: 1794 }, { x: "2017", y: 1794 }, { x: "2018", y: 1794 }, { x: "2019", y: 1794 }, { x: "2020", y: 1794 }, { x: "2021", y: 1828 } ] }, { label: "tax credit", value: [ { x: "2016", y: 8504 }, { x: "2017", y: 6426 }, { x: "2018", y: 5988 }, { x: "2019", y: 5550 }, { x: "2020", y: 5068 }, { x: "2021", y: 4753 } ] }, { label: "net earnings", value: [ { x: "2016", y: 11402 }, { x: "2017", y: 12102 }, { x: "2018", y: 12769 }, { x: "2019", y: 13441 }, { x: "2020", y: 14179 }, { x: "2021", y: 15120 } ] } ]; //Set up stack method var stackIndex=0; var stack = d3.layout.stack() .values(function(d) { return d.value; }) .order("reverse"); //'stack' the data [inject y0 components]; stack(data[0]); stack(data[1]); //extract and parse dates var parseDate = d3.time.format("%Y").parse; var dates = []; data[0].forEach(function(obj,i){ obj.value.forEach(function(vals,i){ vals.x=parseDate(vals.x); dates.push(vals.x); }) }) data[1].forEach(function(obj,i){ obj.value.forEach(function(vals,i){ vals.x=parseDate(vals.x); }) }) //determine max value - need to do this for both var maxValue = 0; data[0].forEach(function(obj,i){ obj.value.forEach(function(vals,i){ maxValue = d3.max([maxValue,(vals.y+vals.y0)]); }) }) //create svg var svg = d3.select("#container").append("svg") .attr("width",width) .attr("height",height) //title etc svg.append("text") .attr("class","source") .attr("x",padding.left) .attr("y",height-10) .text(source); svg.append("text") .attr("id","subtitle") .attr("class","subtitle") .attr("x",padding.left) .attr("y",15) .text(desc[stackIndex]); //Set up scales var xScale = d3.time.scale() .domain(d3.extent(dates)) .range([0,w]); var yScale = d3.scale.linear() .domain([0,maxValue]) .range([h,0]); //Axis generators var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom") var yAxis = d3.svg.axis() .scale(yScale) .orient("left") .tickSize(-w) .ticks(5); //Create axes svg.append("g") .attr("class", "axis x") .attr("transform","translate("+padding.left+","+(height-padding.bottom+5)+")") .call(xAxis); var yAxis = svg.append("g") .attr("class", "axis y") .attr("transform","translate("+padding.left+","+padding.top+")") .call(yAxis); //style 0 line yAxis.selectAll(".tick").filter(function(d, i) { return d==0; }).classed('origin',function(d,i){ return (d == 0); }); //Configure area generator var area = d3.svg.area() .x(function(d) { return xScale(d.x); }) .y0(function(d) { return yScale(d.y0); }) .y1(function(d) { return yScale(d.y0 + d.y); }); //Make a path for each element var pathGrp = svg.append("g") .attr("transform","translate("+padding.left+","+padding.top+")"); var paths = pathGrp.selectAll("path") .data(data[stackIndex]) .enter() .append("path") .attr("class", "area") .attr("d", function(d) { return area(d.value); }) .attr("stroke", "none") .attr("fill", function(d, i) { return colour[i]; }); //labels for stacks var labelGrp = svg.append("g") .attr("transform","translate("+padding.left+","+padding.right+")") var labels = labelGrp.selectAll("text") .data(data[stackIndex]) .enter() .append("text") .attr("text-anchor","middle") .attr("fill",function(d,i){return labelColour[i]}) .attr("x",function(d,i){return labelPlacement[stackIndex][i][0]}) .attr("y",function(d,i){return labelPlacement[stackIndex][i][1]}) .text(function(d){return d.label;}) //annotate totals var commaFormat = d3.format(','); var total = []; total[0] = data[0][0].value[5].y0+data[0][0].value[5].y; total[1] = data[1][0].value[5].y0+data[1][0].value[5].y; var label = svg.append("g").attr("id","anno") .attr("transform","translate("+padding.left+","+padding.top+")") .append("text") .attr("class","value") .attr("x",function(){return 10+xScale(dates[dates.length-1])}) .attr("y",function(){return 5+yScale(total[stackIndex])}) .text("£"+commaFormat(total[stackIndex])) var marker = d3.select("#anno").append("circle") .attr("cx",function(){return xScale(dates[dates.length-1])}) .attr("cy",function(){return yScale(total[stackIndex])}) .attr("r",5) .attr("fill","red"); function changeStack(index){ //rebind data and transition paths.datum(function(d,i){ return data[index][i]; }) .transition() .duration(1000) .attr("d", function(d) { return area(d.value); }) .each("end", function(){ d3.select("#subtitle").text(desc[stackIndex]); }); label.transition() .duration(1000) .attr("y",function(){ return 5+yScale(total[stackIndex]) }) .each("end", function(){ label.text("£"+commaFormat(total[stackIndex])) }); marker.transition() .duration(1000) .attr("cy",function(){ return yScale(total[stackIndex]) }) labels.transition() .duration(1000) .attr("y",function(d,i){return labelPlacement[stackIndex][i][1]}) } setInterval(function(){ if (stackIndex==0){ stackIndex=1; } else { stackIndex=0; }; changeStack(stackIndex) }, 3000); </script> </body> </html>
https://d3js.org/d3.v3.min.js