{"id":"208ac4d6c3f58ab1","slug":"stacked-to-grouped-bars","first_public_version":277,"paused_version":null,"likes":126,"publish_level":"live","forks":170,"fork_of":null,"has_importers":true,"thumbnail":"dd31278bae4c99bde3636bda55fc739876540de74f29d7fb04f30e8d2be11029","default_thumbnail":"dd31278bae4c99bde3636bda55fc739876540de74f29d7fb04f30e8d2be11029","update_time":"2020-08-26T22:54:29.486Z","publish_time":"2023-07-18T12:32:26.946Z","publish_version":281,"latest_version":281,"roles":[],"sharing":null,"owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"},"creator":{"id":"074c414ad1d825f5","avatar_url":"https://avatars.observableusercontent.com/avatar/82811927da99f8938001b2ef1f552ad2c47083e46ebc55a3a146a5a5848c4519","login":"mbostock","name":"Mike Bostock","bio":"Visualization toolmaker. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","tier":"pro"},"authors":[{"id":"074c414ad1d825f5","avatar_url":"https://avatars.observableusercontent.com/avatar/82811927da99f8938001b2ef1f552ad2c47083e46ebc55a3a146a5a5848c4519","name":"Mike Bostock","login":"mbostock","bio":"Visualization toolmaker. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","tier":"pro","approved":true,"description":""}],"files":[],"comments":[],"commenting_lock":null,"suggestions_to":[],"suggestion_from":null,"collections":[{"id":"d9df4fb5263ace62","type":"public","slug":"visualization","title":"Visualization","description":"Explore and explain patterns in quantitative data using D3, Vega, and Observable Plot","update_time":"2023-07-19T17:50:40.230Z","pinned":true,"ordered":true,"custom_thumbnail":"09e385d95ce7df7d392b0133d68e97dd5675378190775d038d21516ea62178ba","default_thumbnail":"32ad600cf556f7b9991b2a11f7b6b8e55abe7ada6062ef88a5a7de422bb261ab","thumbnail":"09e385d95ce7df7d392b0133d68e97dd5675378190775d038d21516ea62178ba","listing_count":84,"parent_collection_count":1,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"1b0fbc3b1939d7fa","type":"public","slug":"gallery","title":"Gallery","description":"Examples featured in the D3 gallery","update_time":"2023-04-10T02:28:10.124Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"6953adf7aec9d29339751fa68d9c9dc67169a0ffd7469f9a4055eda920bdd2a4","thumbnail":"6953adf7aec9d29339751fa68d9c9dc67169a0ffd7469f9a4055eda920bdd2a4","listing_count":147,"parent_collection_count":0,"owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"}},{"id":"5b7cd6cd51908e4d","type":"public","slug":"d3-transition","title":"d3-transition","description":"Animated transitions for D3 selections.","update_time":"2019-01-28T05:17:25.088Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"f594dc193faeb1b248ec303b8b87c958c7eb944a078592ac2ef6feffcc7e0411","thumbnail":"f594dc193faeb1b248ec303b8b87c958c7eb944a078592ac2ef6feffcc7e0411","listing_count":10,"parent_collection_count":1,"owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"}}],"version":245,"title":"Stacked-to-Grouped Bars","license":"isc","copyright":"Copyright 2018–2020 Observable, Inc.","nodes":[{"id":0,"value":"md`# Stacked-to-Grouped Bars\n\nAnimations can preserve object constancy, allowing the reader to follow the data across views. See [Heer and Robertson](http://vis.berkeley.edu/papers/animated_transitions/) for more.`","pinned":false,"mode":"js","data":null,"name":null},{"id":3,"value":"viewof layout = {\n  const form = html`<form style=\"font: 12px var(--sans-serif); display: flex; height: 33px; align-items: center;\">\n  <label style=\"margin-right: 1em; display: inline-flex; align-items: center;\">\n    <input type=\"radio\" name=\"radio\" value=\"stacked\" style=\"margin-right: 0.5em;\" checked> Stacked\n  </label>\n  <label style=\"margin-right: 1em; display: inline-flex; align-items: center;\">\n    <input type=\"radio\" name=\"radio\" value=\"grouped\" style=\"margin-right: 0.5em;\"> Grouped\n  </label>\n</form>`;\n  const interval = setInterval(() => {\n    form.value = form.radio.value = form.radio.value === \"grouped\" ? \"stacked\" : \"grouped\";\n    form.dispatchEvent(new CustomEvent(\"input\"));\n  }, 2000);\n  form.onchange = () => form.dispatchEvent(new CustomEvent(\"input\")); // Safari\n  form.oninput = event => {\n    if (event.isTrusted) clearInterval(interval), form.onchange = null;\n    form.value = form.radio.value;\n  };\n  form.value = form.radio.value;\n  return form;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":60,"value":"chart = {\n  const svg = d3.create(\"svg\")\n      .attr(\"viewBox\", [0, 0, width, height]);\n\n  const rect = svg.selectAll(\"g\")\n    .data(y01z)\n    .join(\"g\")\n      .attr(\"fill\", (d, i) => z(i))\n    .selectAll(\"rect\")\n    .data(d => d)\n    .join(\"rect\")\n      .attr(\"x\", (d, i) => x(i))\n      .attr(\"y\", height - margin.bottom)\n      .attr(\"width\", x.bandwidth())\n      .attr(\"height\", 0);\n\n  svg.append(\"g\")\n      .call(xAxis);\n\n  function transitionGrouped() {\n    y.domain([0, yMax]);\n\n    rect.transition()\n        .duration(500)\n        .delay((d, i) => i * 20)\n        .attr(\"x\", (d, i) => x(i) + x.bandwidth() / n * d[2])\n        .attr(\"width\", x.bandwidth() / n)\n      .transition()\n        .attr(\"y\", d => y(d[1] - d[0]))\n        .attr(\"height\", d => y(0) - y(d[1] - d[0]));\n  }\n\n  function transitionStacked() {\n    y.domain([0, y1Max]);\n\n    rect.transition()\n        .duration(500)\n        .delay((d, i) => i * 20)\n        .attr(\"y\", d => y(d[1]))\n        .attr(\"height\", d => y(d[0]) - y(d[1]))\n      .transition()\n        .attr(\"x\", (d, i) => x(i))\n        .attr(\"width\", x.bandwidth());\n  }\n\n  function update(layout) {\n    if (layout === \"stacked\") transitionStacked();\n    else transitionGrouped();\n  }\n\n  return Object.assign(svg.node(), {update});\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":142,"value":"update = chart.update(layout)","pinned":true,"mode":"js","data":null,"name":null},{"id":75,"value":"xz = d3.range(m) // the x-values shared by all series","pinned":true,"mode":"js","data":null,"name":null},{"id":88,"value":"yz = d3.range(n).map(() => bumps(m)) // the y-values of each of the n series","pinned":true,"mode":"js","data":null,"name":null},{"id":95,"value":"y01z = d3.stack()\n    .keys(d3.range(n))\n  (d3.transpose(yz)) // stacked yz\n  .map((data, i) => data.map(([y0, y1]) => [y0, y1, i]))","pinned":true,"mode":"js","data":null,"name":null},{"id":78,"value":"yMax = d3.max(yz, y => d3.max(y))","pinned":true,"mode":"js","data":null,"name":null},{"id":101,"value":"y1Max = d3.max(y01z, y => d3.max(y, d => d[1]))","pinned":true,"mode":"js","data":null,"name":null},{"id":104,"value":"x = d3.scaleBand()\n    .domain(xz)\n    .rangeRound([margin.left, width - margin.right])\n    .padding(0.08)","pinned":true,"mode":"js","data":null,"name":null},{"id":107,"value":"y = d3.scaleLinear()\n    .domain([0, y1Max])\n    .range([height - margin.bottom, margin.top])","pinned":true,"mode":"js","data":null,"name":null},{"id":110,"value":"z = d3.scaleSequential(d3.interpolateBlues)\n    .domain([-0.5 * n, 1.5 * n])","pinned":true,"mode":"js","data":null,"name":null},{"id":178,"value":"xAxis = svg => svg.append(\"g\")\n    .attr(\"transform\", `translate(0,${height - margin.bottom})`)\n    .call(d3.axisBottom(x).tickSizeOuter(0).tickFormat(() => \"\"))","pinned":true,"mode":"js","data":null,"name":null},{"id":62,"value":"n = 5 // number of series","pinned":true,"mode":"js","data":null,"name":null},{"id":66,"value":"m = 58 // number of values per series","pinned":true,"mode":"js","data":null,"name":null},{"id":113,"value":"height = 500","pinned":true,"mode":"js","data":null,"name":null},{"id":118,"value":"margin = ({top: 0, right: 0, bottom: 10, left: 0})","pinned":true,"mode":"js","data":null,"name":null},{"id":69,"value":"// Returns an array of m psuedorandom, smoothly-varying non-negative numbers.\n// Inspired by Lee Byron’s test data generator.\n// http://leebyron.com/streamgraph/\nfunction bumps(m) {\n  const values = [];\n\n  // Initialize with uniform random values in [0.1, 0.2).\n  for (let i = 0; i < m; ++i) {\n    values[i] = 0.1 + 0.1 * Math.random();\n  }\n\n  // Add five random bumps.\n  for (let j = 0; j < 5; ++j) {\n    const x = 1 / (0.1 + Math.random());\n    const y = 2 * Math.random() - 0.5;\n    const z = 10 / (0.1 + Math.random());\n    for (let i = 0; i < m; i++) {\n      const w = (i / m - y) * z;\n      values[i] += x * Math.exp(-w * w);\n    }\n  }\n\n  // Ensure all values are positive.\n  for (let i = 0; i < m; ++i) {\n    values[i] = Math.max(0, values[i]);\n  }\n\n  return values;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":82,"value":"d3 = require(\"d3@6\")","pinned":true,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}