block by micahstubbs 02f882c439e931c50e26be8dc403a775

Stacked-to-Grouped | JSON Data, Horizontal Bars, String Series Names

Full Screen

switch between stacked and grouped layouts using sequenced transitions ✨

animations preserve object constancy and allow the user to follow the data across views

animation design by Heer and Robertson inspired by Byron and Wattenberg

this iteration draws horizontal bars from static data that have strings for the series names

an ES2015 implementation in something like the airbnb style

inspired by Stacked-to-Grouped Bars from @mbostock

custom radio buttons from the codepen Pure CSS & HTML cross-browser elegant Radio Buttons from knitevision1

you can also view this set of experiments in github repo form

👏 to @alexmacy for wading through a few thousand transition errors to help me find the data-prep 🐛 causing them all

Stacked-to-Grouped | Generated Data, Vertical Bars
Stacked-to-Grouped | JSON Data, Vertical Bars
Stacked-to-Grouped | Generated Data, Horizontal Bars
Stacked-to-Grouped | JSON Data, Horizontal Bars
Stacked-to-Grouped | JSON Data, Horizontal Bars, String Series Names

index.html

<!DOCTYPE html>
<meta charset='utf-8'>
<style>
/* Radio button */
.custom-radio input[type="radio"] {
    display:none;
}
.custom-radio input[type="radio"] + label {
    font-size:14px;
    color: black;
    cursor: pointer;
}
.custom-radio input[type="radio"] + label span {
    display:inline-block;
    width:10px;
    height:10px;
    margin:-1px 4px 0 0;
    vertical-align:middle;
    cursor:pointer;
    -moz-border-radius:  50%;
    border-radius:  50%;
}
.custom-radio input[type="radio"] + label span {
    background-color: #c4c4c4; /* have to be of the same color */
    border: 2px solid #c4c4c4; /* have to be of the same color */
}
/* Checked state for radio */
.custom-radio input[type='radio']:checked + label span{
     background-color: #31A354;
}
</style>
<div class='custom-radio'>
  <div>
    <input type='radio' id='grouped' name='mode' value='grouped'>
    <label for='grouped'><span></span> Grouped</label>
  </div>
  <div>
    <input type='radio' id='stacked' name='mode' value='stacked' checked>
    <label for='stacked'><span></span> Stacked</label>
  </div>
</div>
<svg width='960' height='1200'></svg>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://d3js.org/d3-queue.v2.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js'></script>
<script src='vis.js' lang='babel' type='text/babel'></script>

data.json

[
    {
        "0": 0.8345658418026087,
        "1": 2.16990473059691,
        "2": 0.11225498002657552,
        "3": 0.17486306808582253,
        "name": "zero"
    },
    {
        "0": 0.4547571633367139,
        "1": 2.293639687510199,
        "2": 0.11257016829714184,
        "3": 0.2298141045051727,
        "name": "one"
    },
    {
        "0": 0.190595042313489,
        "1": 2.2298279573365214,
        "2": 0.1839374938020395,
        "3": 0.3360952676772113,
        "name": "two"
    },
    {
        "0": 0.19738488101007876,
        "1": 2.206696498843617,
        "2": 0.17533589513765335,
        "3": 0.5859787329034409,
        "name": "three"
    },
    {
        "0": 0.109208829080153,
        "1": 1.9552149026588583,
        "2": 0.11277306719085622,
        "3": 0.9600889268112711,
        "name": "four"
    },
    {
        "0": 0.1061610735747754,
        "1": 1.5905810961242883,
        "2": 0.13741060343967204,
        "3": 1.1570885127433614,
        "name": "five"
    },
    {
        "0": 0.16451368843759093,
        "1": 1.2859246485063744,
        "2": 0.10184544036173926,
        "3": 1.1241807682264733,
        "name": "six"
    },
    {
        "0": 0.1025131649061017,
        "1": 0.9717402103146218,
        "2": 0.12968887820309827,
        "3": 0.8727085031257851,
        "name": "seven"
    },
    {
        "0": 0.1949447302325289,
        "1": 0.6992763765691113,
        "2": 0.17971966572582898,
        "3": 0.517827011694173,
        "name": "eight"
    },
    {
        "0": 0.1177193520601679,
        "1": 0.46505701727659365,
        "2": 0.11270388919011565,
        "3": 0.3260373543877625,
        "name": "nine"
    },
    {
        "0": 0.1462059409009294,
        "1": 0.3094175422555735,
        "2": 0.16167759830492812,
        "3": 0.1715670622073826,
        "name": "ten"
    },
    {
        "0": 0.1851331271609325,
        "1": 0.24030077931859933,
        "2": 0.1011320269253213,
        "3": 0.18588128995552966,
        "name": "eleven"
    },
    {
        "0": 0.18106955819260212,
        "1": 0.22449970770786298,
        "2": 0.14461975744106742,
        "3": 0.13603826586713308,
        "name": "twelve"
    },
    {
        "0": 0.17837297705138078,
        "1": 0.18174301488576006,
        "2": 0.15011967618163913,
        "3": 0.17794511739155605,
        "name": "thirteen"
    },
    {
        "0": 0.18259683666081902,
        "1": 0.1847859732797195,
        "2": 0.17994292592532157,
        "3": 0.19901139192343287,
        "name": "fourteen"
    },
    {
        "0": 0.1110130198686905,
        "1": 0.1436178823525508,
        "2": 0.10628027401737211,
        "3": 0.1814916926446018,
        "name": "fifteen"
    },
    {
        "0": 0.11361529035333096,
        "1": 0.15947784914079133,
        "2": 0.11441050324234148,
        "3": 0.13875245625206478,
        "name": "sixteen"
    },
    {
        "0": 0.14790163702068787,
        "1": 0.19014909804324626,
        "2": 0.1555474119041413,
        "3": 0.20840404337267765,
        "name": "seventeen"
    },
    {
        "0": 0.1949968036066766,
        "1": 0.29270545322394426,
        "2": 0.114201446093528,
        "3": 0.20711288514200954,
        "name": "eighteen"
    },
    {
        "0": 0.16061663536254375,
        "1": 0.4488311604689811,
        "2": 0.19108732592581135,
        "3": 0.29196460596774787,
        "name": "nineteen"
    },
    {
        "0": 0.11828970638524117,
        "1": 0.6022499142439799,
        "2": 0.1906754956974005,
        "3": 0.44976999290236486,
        "name": "twenty"
    },
    {
        "0": 0.19052420051116256,
        "1": 0.7586339982956414,
        "2": 0.18611717332528988,
        "3": 0.7135240637821088,
        "name": "twenty-one"
    },
    {
        "0": 0.14102757049524564,
        "1": 1.0436032784557587,
        "2": 0.21383567141546456,
        "3": 0.9105069969517476,
        "name": "twenty-two"
    },
    {
        "0": 0.1400763509851857,
        "1": 1.2529114940133406,
        "2": 0.16792258073092658,
        "3": 1.2392922805597992,
        "name": "twenty-three"
    },
    {
        "0": 0.17145341173755935,
        "1": 1.369028294902841,
        "2": 0.2554756001305245,
        "3": 1.4390036686572394,
        "name": "twenty-four"
    },
    {
        "0": 0.1142197822651475,
        "1": 1.3204483212776335,
        "2": 0.2512993182290364,
        "3": 1.6674115370963114,
        "name": "twenty-five"
    },
    {
        "0": 0.15427104036012443,
        "1": 1.2467959228758292,
        "2": 0.4447079455959,
        "3": 1.7608832243905004,
        "name": "twenty-six"
    },
    {
        "0": 0.18941420012129426,
        "1": 0.9566411058342233,
        "2": 0.6934709075960008,
        "3": 1.6711918800408099,
        "name": "twenty-seven"
    },
    {
        "0": 0.16030801532958389,
        "1": 0.72968388523596,
        "2": 1.2456663863075754,
        "3": 1.449647296489422,
        "name": "twenty-eight"
    },
    {
        "0": 0.1612989393999626,
        "1": 0.5232221990536086,
        "2": 2.0069308208914673,
        "3": 1.2387292588529517,
        "name": "twenty-nine"
    },
    {
        "0": 0.1680850695478585,
        "1": 0.4098415290607861,
        "2": 2.8534512729434294,
        "3": 0.9218047337208097,
        "name": "thirty"
    },
    {
        "0": 0.1167075659137578,
        "1": 0.2243459400027773,
        "2": 3.4631976204576547,
        "3": 0.6533988575872596,
        "name": "thirty-one"
    },
    {
        "0": 0.14647393431706796,
        "1": 0.2458810499577117,
        "2": 3.4525744339678957,
        "3": 0.4886764645805308,
        "name": "thirty-two"
    },
    {
        "0": 0.1278677185146747,
        "1": 0.18830239395443316,
        "2": 3.1065773115746826,
        "3": 0.37642130506603877,
        "name": "thirty-three"
    },
    {
        "0": 0.1391673391797843,
        "1": 0.11174942607313926,
        "2": 2.7357288662136545,
        "3": 0.23271637827985625,
        "name": "thirty-four"
    },
    {
        "0": 0.2507616269855116,
        "1": 0.13015386121548111,
        "2": 2.541133252201119,
        "3": 0.22131639202239528,
        "name": "thirty-five"
    },
    {
        "0": 0.26073384485168427,
        "1": 0.13446671655256653,
        "2": 2.5342757106187426,
        "3": 0.19129613570397597,
        "name": "thirty-six"
    },
    {
        "0": 0.3255208118456826,
        "1": 0.14310755099573258,
        "2": 2.531776208280289,
        "3": 0.20346806297265152,
        "name": "thirty-seven"
    },
    {
        "0": 0.49053994714140703,
        "1": 0.1833573284332347,
        "2": 2.5515216319513385,
        "3": 0.16097417163299932,
        "name": "thirty-eight"
    },
    {
        "0": 0.6566616225859324,
        "1": 0.1420633692087001,
        "2": 2.566489413584417,
        "3": 0.19608224521048845,
        "name": "thirty-nine"
    },
    {
        "0": 0.7906099471022422,
        "1": 0.13088816206384812,
        "2": 2.658579431787212,
        "3": 0.1449285983263784,
        "name": "forty"
    },
    {
        "0": 0.9807878060688358,
        "1": 0.10349600956435734,
        "2": 2.678643544449402,
        "3": 0.14288310732988485,
        "name": "forty-one"
    },
    {
        "0": 1.158566848273416,
        "1": 0.14552089136288648,
        "2": 2.8595505453152823,
        "3": 0.1201609266976762,
        "name": "forty-two"
    },
    {
        "0": 1.3158716481832025,
        "1": 0.11165480642176173,
        "2": 2.9227109679896137,
        "3": 0.18328416679433346,
        "name": "forty-three"
    },
    {
        "0": 1.3141794328922924,
        "1": 0.17506966697906257,
        "2": 3.0551860629320835,
        "3": 0.17020174336390603,
        "name": "forty-four"
    },
    {
        "0": 1.2835620009724025,
        "1": 0.15243850480914034,
        "2": 3.149823756134488,
        "3": 0.19692156360240798,
        "name": "forty-five"
    },
    {
        "0": 1.1763590731124858,
        "1": 0.10565073069580855,
        "2": 3.085488798263314,
        "3": 0.10721725212540267,
        "name": "forty-six"
    },
    {
        "0": 1.0247175741491994,
        "1": 0.13738228419716658,
        "2": 2.9246150406007576,
        "3": 0.1632273588191122,
        "name": "forty-seven"
    },
    {
        "0": 0.8811955960917054,
        "1": 0.12676860140735136,
        "2": 2.674648207968276,
        "3": 0.17207439227140098,
        "name": "forty-eight"
    },
    {
        "0": 0.7063281912406454,
        "1": 0.16015511531309168,
        "2": 2.3763529091188613,
        "3": 0.16109040013513898,
        "name": "forty-nine"
    },
    {
        "0": 0.5449865739415336,
        "1": 0.19760568934581513,
        "2": 2.0049745878747496,
        "3": 0.30556676529604854,
        "name": "fifty"
    },
    {
        "0": 0.4121531564634099,
        "1": 0.11815561152617993,
        "2": 1.599948087918452,
        "3": 0.5647793548545561,
        "name": "fifty-one"
    },
    {
        "0": 0.32214329647797335,
        "1": 0.12102494255479967,
        "2": 1.2459420697339225,
        "3": 1.0819651957138756,
        "name": "fifty-two"
    },
    {
        "0": 0.27217968508738444,
        "1": 0.19183100050676083,
        "2": 0.8911795060576224,
        "3": 1.831423637178713,
        "name": "fifty-three"
    },
    {
        "0": 0.1861140478814755,
        "1": 0.1623990293221626,
        "2": 0.642617369968976,
        "3": 2.401284906380081,
        "name": "fifty-four"
    },
    {
        "0": 0.12542299658774217,
        "1": 0.14343688785307282,
        "2": 0.49382970229231415,
        "3": 2.2719496090564806,
        "name": "fifty-five"
    },
    {
        "0": 0.14181692551789446,
        "1": 0.18442442382104077,
        "2": 0.3469738553459,
        "3": 1.6072251382480411,
        "name": "fifty-six"
    },
    {
        "0": 0.1905082555552165,
        "1": 0.15980815514017616,
        "2": 0.26596917763541894,
        "3": 0.9092048653077827,
        "name": "fifty-seven"
    }
]

vis.js

const queue = d3_queue.queue();

queue
  .defer(d3.json, 'data.json')
  .await(render);

function render(error, data) {
  const seriesKeys = Object.keys(data[0]).slice(0, Object.keys(data[0]).length - 1);
  console.log('Object.keys(data[0])', Object.keys(data[0]));
  console.log('seriesKeys', seriesKeys);

  const stackedData = d3.stack()
    .keys(seriesKeys)(data);

  const xMaxGrouped = d3.max(data, d => d3.max(Object.values(d).filter(e => typeof e !== 'string')));
  const xMaxStacked = d3.max(data, d => d3.sum(Object.values(d).filter(e => typeof e !== 'string')));
  const n = seriesKeys.length; // the number of series
  const yValuesDomain = d3.range(data.length); // the number of values per series
  const yLabelsDomain = stackedData[0].map(d => d.data.name);


  console.log('stackedData', stackedData);
  console.log('xMaxGrouped', xMaxGrouped);
  console.log('xMaxStacked', xMaxStacked);
  console.log('n, the number of series', n);
  console.log('yValuesDomain', yValuesDomain);
  console.log('yLabelsDomain', yLabelsDomain);

  const svg = d3.select('svg');
  const controlHeight = 50;
  const margin = {top: 10, right: 10, bottom: 20, left: 60};
  const width = +svg.attr('width') - margin.left - margin.right;
  const height = +svg.attr('height') - controlHeight - margin.top - margin.bottom;
  const g = svg.append('g')
    .attr('transform', `translate(${margin.left},${margin.top})`);

  const x = d3.scaleLinear()
    .domain([0, xMaxStacked])
    .range([0, width]);

  const yValuesScale = d3.scaleBand()
    .domain(yValuesDomain)
    .rangeRound([0, height])
    .padding(0.08);

  const yLabelsScale = d3.scaleBand()
    .domain(yLabelsDomain)
    .rangeRound([0, height])
    .padding(0.08);

  const color = d3.scaleOrdinal()
    .domain(d3.range(n))
    .range(d3.schemeCategory20c.slice(8, 12)); // greens

  const series = g.selectAll('.series')
    .data(stackedData)
    .enter().append('g')
      .attr('fill', (d, i) => color(i));

  const rect = series.selectAll('rect')
    .data(d => d)
    .enter().append('rect')
      .attr('x', 0)
      .attr('y', (d, i) => yValuesScale(i))
      .attr('width', 0)
      .attr('height', yValuesScale.bandwidth());

  rect.transition()
    .delay((d, i) => i * 10)
    .attr('x', d => x(d[0]))
    .attr('y', (d, i) => yValuesScale(i))
    .attr('width', d => {
      return x(d[1]) - x(d[0]);
    });

  g.append('g')
    .attr('class', 'axis axis--y')
    .attr('transform', `translate(0,0)`)
    .call(d3.axisLeft(yLabelsScale)
      .tickSize(0)
      .tickPadding(6)
    );

  d3.selectAll('input')
    .on('change', changed);

  // change to grouped once
  let timeout = d3.timeout(() => {
    d3.select('input[value=\'grouped\']')
      .property('checked', true)
      .dispatch('change');
  }, 2000);

  // it looks like the error messages originate
  // from the tweening that d3 does on the transition
  function changed() {
    timeout.stop();
    if (this.value === 'grouped') transitionGrouped();
    else transitionStacked();
  }

  function transitionGrouped() {
    x.domain([0, xMaxGrouped]);

    rect.transition()
      .duration(500)
      .delay((d, i) => i * 10)
      .attr('y', function(d, i) {
        // console.log('n from rect.transition', n);
        // console.log('this.parentNode.__data__.key', d3.select(this.parentNode).data());
        return yValuesScale(i) + yValuesScale.bandwidth() / n * +d3.select(this.parentNode).data()[0].index;
      })
      .attr('height', yValuesScale.bandwidth() / n)
      .transition()
      .delay((d, i) => 1000 + i * 10)
        .attr('x', d => x(0))
        .attr('width', d => x(0) + x(d[1] - d[0]));
  }

  function transitionStacked() {
    x.domain([0, xMaxStacked]);

    rect.transition()
      .duration(500)
      .delay((d, i) => i * 10)
      .attr('x', d => x(d[0]))
      .attr('width', d => x(d[1]) - x(d[0]))
      .transition()
      .delay((d, i) => 1000 + i * 10)
        .attr('y', (d, i) => yValuesScale(i))
        .attr('height', yValuesScale.bandwidth());
  }
}