block by jeremycflin daa2495d00d2f07c34b7b3dac176a356

Surplus/deficit filled line II

Full Screen

Another example of a surplus/deficit filled line chart. The colour of each section encodes the party that was in power during that period. Uses d3-annotation for labels on hover.

See Surplus/deficit filled line I for how I use a mask colour deficit and surplus areas separately.

This is part of a series of visualisations called My Visual Vocabulary which aims to recreate every visualisation in the FT’s Visual Vocabulary from scratch using D3.

forked from tlfrd‘s block: Surplus/deficit filled line II

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="d3-annotation.min.js"></script>
  <style>
    body { margin:0; position:fixed; top:0; right:0; bottom:0; left:0; }
    
    .surplus-deficit-area {
      fill: black;
      opacity: 0.1;
    }
    
    .surplus-deficit-line { 
      fill: none;
      stroke: black;
    }
    
    .title {
      font-family: sans-serif;
    }
    
    .annotation-note text {
      fill: black;
      font-family: sans-serif;
      font-size: 12px;
    }
    
    .annotation-connector path {
      stroke: black;
    }
    
    rect.annotation-note-bg {
    	fill-opacity: 0.75;   
    }
    
  </style>
</head>

<body>
  <script>
		var margin = {top: 100, right: 100, bottom: 100, left: 100};
    
    var width = 960 - margin.left - margin.right,
    		height = 500 - margin.top - margin.bottom;
    
    var svg = d3.select("body").append("svg")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
        .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var defs = svg.append("defs");
    
    var parseYear = d3.timeParse("%Y");
    var parseDate = d3.timeParse("%d-%m-%Y");
    
    var partyToColour = {
      "Labour": "#d50000",
      "Conservative": "#0087dc",
      "Conservative & Liberal Democrat": "#0087dc"
    };
    
    var x = d3.scaleTime()
    	.range([0, width]);
    
    var y = d3.scaleLinear()
    	.range([height, 0]);
    
    var xAxis = d3.axisBottom()
    	.scale(x);
    
    var yAxis = d3.axisLeft()
    	.scale(y);
    
    function parseBudgetDeficit(d) { 
      return {
        date: parseYear(d.year),
        deficit: +d.deficit_percent
      };
    }
    
    function parseGovernments(d) {
      return {
        party: d.party,
        leader: d.leader,
        startDate: parseDate(d.start),
        endDate: parseDate(d.end)
      }
    }
    
    var area = d3.area()
      .x(function(d) { return x(d.date); })
      .y1(function(d) { return y(d.deficit); });
    
    var line = d3.line()
    	.x(function(d) { return x(d.date); })
    	.y(function(d) { return y(d.deficit); });
    
    d3.queue()
    	.defer(d3.csv, "budget-deficit-gdp.csv", parseBudgetDeficit)
    	.defer(d3.csv, "governments.csv", parseGovernments)
    	.await(ready);
    
    function ready(error, data, govs) {
      if (error) throw error;
      
      x.domain(d3.extent(data, function(d) { return d.date; }));
      
      y.domain([Math.floor(d3.min(data, function(d) { return d.deficit; })),
               	Math.ceil(d3.max(data, function(d) { return d.deficit; }))]);
      
      govs = govs.filter(function(g) {
        return g.startDate < x.domain()[1];
      });
      
      area.y0(y(0));
      
      defs.append("mask")
      	.attr("id", "deficitMask")
      	.append("path")
      	.attr("d", function() {
        	return area(data);
      	})
      	.attr("fill", "white")
      	.attr("opacity", 0.8);
      
      defs.append("clipPath")
      	.attr("id", "clipAxes")
      	.append("rect")
      	.attr("width", width)
      	.attr("height", height);
      
      var governments = svg.append("g")
      	.attr("class", "governments")
      	.attr("clip-path", "url(#clipAxes)");
      
      governments.selectAll("rect")
      	.data(govs)
      .enter().append("rect")
      	.attr("x", function(d) {
        	return x(d.startDate);
      	})
      	.attr("y", 0)
      	.attr("width", function(d, i) {
            return x(d.endDate) - x(d.startDate) + 0.2;
      	})
      	.attr("height", height)
      	.attr("fill", function(d) {
        	return partyToColour[d.party];
      	})
      	.attr("mask", "url(#deficitMask)")
      	.on("mouseover", function(d) {
        	showLabel(d);
     		})
      	.on("mouseout", function() {
        	// should think of a better way of doing this
        	// ideally should be showing/hiding not appending/removing
        	d3.select(".annotation-group").remove();
      	})
      
      svg.append("path")
      	.datum(data)
      	.attr("class", "surplus-deficit-line")
				.attr("d", line)
      
      svg.append("g")
      	.attr("class", "x axis")
      	.attr("transform", "translate(0," + height + ")")
      	.call(xAxis);
      
      svg.append("text")
      	.attr("class", "title")
      	.attr("transform", "translate(" + width / 2 + ",0)")
      	.attr("dy", "-2em")
      	.attr("text-anchor", "middle")
      	.text("UK government deficit / surplus as percentage of GDP");
      
      svg.append("g")
      	.attr("class", "y axis")
      	.call(yAxis);
      
      function showLabel(g) {
       	var annotations = [{
          note: { 
            label: g.leader,
          	title: g.party
          },
          data: { 
            start: g.startDate,
            end: g.endDate,
          },
          subject: {
            x1: x(g.startDate),
            x2: x(g.endDate)
          },
          dy: -50,
          dx: 10
        }]
        
        var makeAnnotations = d3.annotation()
      	.type(d3.annotationXYThreshold)
      	.accessors({
        	x: function(d) {
            return (x(d.start) + x(d.end)) / 2;
          },
        	y: function() {
            return y(0);
					}
      	})
      	.annotations(annotations);
        
    		var annotationsGroup = svg.append("g")
    			.attr("class", "annotation-group")
        	.call(makeAnnotations);
      }
      
    }

  </script>
</body>

budget-deficit-gdp.csv

year,deficit_percent
1947,3.16
1948,-1.55
1949,-5.43
1950,-6.33
1951,-5.62
1952,-4.5
1953,-3.23
1954,-2.59
1955,-2.48
1956,-3.53
1957,-2.99
1958,-3.16
1959,-2.94
1960,-1.1
1961,-0.92
1962,-1.87
1963,-2
1964,-2.14
1965,-3.47
1966,-3.88
1967,-3.87
1968,-3.53
1969,-5.98
1970,-7.6
1971,-6.68
1972,-4.25
1973,-2.19
1974,-1.04
1975,0.13
1976,0.71
1977,0.39
1978,0.69
1979,1.75
1980,1.19
1981,2.24
1982,0.66
1983,0.79
1984,1.28
1985,1.44
1986,0.69
1987,0.83
1988,0
1989,-1.87
1990,-1.68
1991,-0.67
1992,1.54
1993,5.06
1994,5.72
1995,4.3
1996,2.91
1997,2.33
1998,0.18
1999,-1.08
2000,-2.14
2001,-2.55
2002,-1.19
2003,0.98
2004,1.22
2005,1.39
2006,0.89
2007,0.54
2008,0.67
2009,3.84
2010,6.53
2011,5.79
2012,4.97
2013,4.91
2014,4.01
2015,3.15
2016,2.14
2017,0.72

governments.csv

party,leader,start,end
Labour,Clement Atlee,26-06-1945,26-10-1951
Conservative,Sir Winston Churchill,26-10-1951,06-04-1955
Conservative,Sir Anthony Eden,06-04-1955,10-01-1957
Conservative,Harold Macmillian,10-01-1957,19-10-1963
Conservative,Sir Alec Douglas-Home,19-10-1963,16-10-1964
Labour,Harold Wilson,16-10-1964,19-06-1970
Conservative,Edward Heath,19-06-1970,04-03-1974
Labour,Harold Wilson,04-03-1974,05-04-1976
Labour,James Callaghan,05-04-1976,04-05-1979
Conservative,Margaret Thatcher,04-05-1979,28-11-1990
Conservative,John Major,28-11-1990,02-05-1997
Labour,Tony Blair,02-05-1997,27-06-2007
Labour,Gordon Brown,27-06-2007,11-05-2010
Conservative & Liberal Democrat,David Cameron,11-05-2010,08-05-2015
Conservative,David Cameron,08-05-2015,13-07-2016
Conservative,Theresa May,13-07-2016,11-06-2017
Conservative,Theresa May,11-06-2017,