block by jonsadka 07202c6312437c896ac1dc2ba1c6dbd4

Individual to stacked area charts

Full Screen

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
  <style>
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
    .x-axis .tick line {
      stroke: #EFEFEF;
    }
    path {
      animation: dash 2s linear infinite;
    }
    @keyframes dash {
      to {
        stroke-dashoffset: -16;
      }
    }
  </style>
</head>

<body>
  <svg width="960" height="500"></svg>
  <script> 
    function generateRandomData(years){
      var randomData = [];
      for (var i = 0; i < years; i++){
        randomData.push({type: 'local', year: 2000 + i, taxRate: _randomLocalData()});
        randomData.push({type: 'state', year: 2000 + i, taxRate: _randomStateData()});
        randomData.push({type: 'federal', year: 2000 + i, taxRate: _randomFederalData()});
      }
      return randomData;

      function _randomLocalData(){return Math.min(0.04, Math.random()/10);}
      function _randomStateData(){return Math.min(0.11, Math.random()/8);}
      function _randomFederalData(){return Math.min(0.25, Math.random()/4);}
    }
  </script>
  <script>
    ////// COMPOSE DATA
    var taxData = generateRandomData(20, 100000);
    var taxDataByYear = d3.nest()
      .key(function(d){return d.year;})
      .entries(taxData);
    var taxDataByType = d3.nest()
      .key(function(d){return d.type;})
      .entries(taxData);
    var taxDataForStack = taxDataByYear.reduce(function(taxData, data){
      var newData = {year: data.key};
      data.values.forEach(function(d){newData[d.type] = d.taxRate;});
      taxData.push(newData);
      return taxData;
    }, [])
    var years = taxDataByYear.map(function(d){return +d.key;})
    var localTaxData = taxDataByType[0].values;
    var stateTaxData = taxDataByType[1].values;
    var federalTaxData = taxDataByType[2].values;
    
    ////// SETUP CONTAINERS
    var svg = d3.select('svg')
    var margin = {top: 40, right: 40, bottom: 50, left: 60};
    var height = +svg.attr('height') - margin.top - margin.bottom;
    var width = +svg.attr('width') - margin.left - margin.right;
    var chartPadding = 15;
    var chartHeight = height / taxDataByType.length - 15;

    ////// COMPOSE GENERATORS
    var generators = {
      'shared': {
        color: d3.scaleOrdinal(d3.schemePastel1).domain(['local', 'state', 'federal']),
        stack: d3.stack().keys(['local', 'state', 'federal']).order(d3.stackOrderDescending).offset(d3.stackOffsetNone),
        x: d3.scaleLinear().domain([d3.min(years), d3.max(years)]).range([0, width]),
        y: d3.scaleLinear().domain([d3.max(federalTaxData, function(d){return d.taxRate}), 0]).range([0, height])
      },
      'local': {},
      'state': {},
      'federal': {y: d3.scaleLinear().domain([d3.max(federalTaxData, function(d){return d.taxRate}), 0]).range([0, chartHeight])}
    };
    var stackTaxData = generators.shared.stack(taxDataForStack);
    var maxStackData = d3.max(stackTaxData, function(d){return d3.max(d, function(d){return d[1];});});
    
    generators.shared.xAxis = d3.axisBottom(generators.shared.x).tickValues(years).tickFormat(d3.format('.4r')).tickSizeInner(-height);
    generators.shared.y = d3.scaleLinear().domain([maxStackData, 0]).range([0, height]);
    generators.shared.yAxis = d3.axisLeft(generators.shared.y).tickFormat(d3.format('.2f'));
    generators.shared.area = d3.area().x(function(d){return generators.shared.x(d.data.year);})
      .y0(function(d){return generators.shared.y(d[0]);}).y1(function(d){return generators.shared.y(d[1]);})
      .curve(d3.curveMonotoneX)
    
    generators.local.area = d3.area(localTaxData).x(function(d){return generators.shared.x(d.year);})
      .y0(function(d){return generators.federal.y(0);}).y1(function(d){return generators.federal.y(d.taxRate);})
      .curve(d3.curveMonotoneX);
    
    generators.state.area = d3.area(stateTaxData).x(function(d){return generators.shared.x(d.year);})
      .y0(function(d){return generators.federal.y(0);}).y1(function(d){return generators.federal.y(d.taxRate);})
      .curve(d3.curveMonotoneX);
    
    generators.federal.yAxis = d3.axisLeft(generators.federal.y).ticks(4).tickFormat(d3.format('.2f'));
    generators.federal.area = d3.area(federalTaxData).x(function(d){return generators.shared.x(d.year);})
      .y0(function(d){return generators.federal.y(0);}).y1(function(d){return generators.federal.y(d.taxRate);})
      .curve(d3.curveMonotoneX);

    ////// MAKE IT ALIVE!
    var grouping = svg.append('g')
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

    grouping.append('g')
      .call(generators.shared.xAxis)
      .attr('transform', 'translate(0,' + height + ')')
      .attr('class', 'x-axis');

    containers = grouping.selectAll('.containers').data(taxDataByType).enter()
      .append('g')
        .attr('transform', function(d, i){ return 'translate(0, ' + ((chartHeight + chartPadding) * i) + ')'; })
        .attr('class', 'containers')
    containers.append('g')
        .attr('class', 'y-axis')
        .call(generators.federal.yAxis)
    containers.append('path')
        .attr('d', function(d){return generators[d.key].area(d.values);})
        .attr('class', 'stack-container')
        .attr('stroke', 'gray')
        .attr('stroke-dasharray', '8 8')
        .attr('fill', function(d){return generators.shared.color(d.key)})

    var stacked = false;
    setInterval(function(){
      var selection = d3.selectAll('.containers')
      if (stacked){
        selection.data(taxDataByType);
        
        selection
          .transition().duration(1000)
            .attr('transform', function(d, i){ return 'translate(0, ' + ((chartHeight + chartPadding) * i) + ')'; })
          .select('.y-axis')
            .call(generators.federal.yAxis);

        selection.select('.stack-container')
          .transition().duration(1000)
            .attr('d', function(d){return generators[d.key].area(d.values);});
      } else {
        selection.data(stackTaxData);

        selection
          .transition().duration(1000)
            .attr('transform', 'translate(0,0)')
          .select('.y-axis')
            .call(generators.shared.yAxis)

        selection.select('.stack-container')
          .transition().duration(1000)
            .attr('d', generators.shared.area);
      }
      stacked = !stacked;
    }, 2000)
  </script> 
</body>