Update pattern for subplots

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
1
down vote

favorite












I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).



d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.



I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.






const dataset1 = [
"division": "division 1", "team": "team 1", "value": 5,
"division": "division 1", "team": "team 1", "value": 7,
"division": "division 1", "team": "team 1", "value": 9,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 2", "value": 1,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 1", "team": "team 3", "value": 1,
"division": "division 1", "team": "team 3", "value": 7,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 1", "value": 7,
"division": "division 2", "team": "team 1", "value": 3,
"division": "division 2", "team": "team 2", "value": 6,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 2,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 9,
"division": "division 2", "team": "team 3", "value": 5,
]

const dataset2 = [
"division": "division 1", "team": "team 1", "value": 1,
"division": "division 1", "team": "team 1", "value": 4,
"division": "division 1", "team": "team 1", "value": 3,
"division": "division 1", "team": "team 2", "value": 6,
"division": "division 1", "team": "team 2", "value": 2,
"division": "division 1", "team": "team 2", "value": 9,
"division": "division 1", "team": "team 3", "value": 5,
"division": "division 1", "team": "team 3", "value": 2,
"division": "division 1", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 1", "value": 8,
"division": "division 2", "team": "team 1", "value": 1,
"division": "division 2", "team": "team 1", "value": 2,
"division": "division 2", "team": "team 2", "value": 5,
"division": "division 2", "team": "team 2", "value": 3,
"division": "division 2", "team": "team 2", "value": 7,
"division": "division 2", "team": "team 3", "value": 4,
"division": "division 2", "team": "team 3", "value": 3,
"division": "division 2", "team": "team 3", "value": 8,
]

function updateChart(data)

// Divisions
var divisions = d3.nest().key(d=>d.division).entries(data);
var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);

// Teams
var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
.data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);

// Values for each team
var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
var barsEnter = bars.enter()
.append("rect")
.attr("x", (d,i)=>i*12)
.attr("width", 10)
bars.merge(barsEnter)
.transition()
.attr("height", d=>d.value*10)

// Toggle dataset
currDataset = data == dataset1 ? dataset2 : dataset1;


let currDataset = dataset1;
updateChart(currDataset);

d3.select("#updateData").on("click", () => updateChart(currDataset))

<p><button id="updateData">Update Data</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>









share|improve this question



























    up vote
    1
    down vote

    favorite












    I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).



    d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.



    I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.






    const dataset1 = [
    "division": "division 1", "team": "team 1", "value": 5,
    "division": "division 1", "team": "team 1", "value": 7,
    "division": "division 1", "team": "team 1", "value": 9,
    "division": "division 1", "team": "team 2", "value": 2,
    "division": "division 1", "team": "team 2", "value": 1,
    "division": "division 1", "team": "team 2", "value": 1,
    "division": "division 1", "team": "team 3", "value": 3,
    "division": "division 1", "team": "team 3", "value": 1,
    "division": "division 1", "team": "team 3", "value": 7,
    "division": "division 2", "team": "team 1", "value": 2,
    "division": "division 2", "team": "team 1", "value": 7,
    "division": "division 2", "team": "team 1", "value": 3,
    "division": "division 2", "team": "team 2", "value": 6,
    "division": "division 2", "team": "team 2", "value": 3,
    "division": "division 2", "team": "team 2", "value": 2,
    "division": "division 2", "team": "team 3", "value": 3,
    "division": "division 2", "team": "team 3", "value": 9,
    "division": "division 2", "team": "team 3", "value": 5,
    ]

    const dataset2 = [
    "division": "division 1", "team": "team 1", "value": 1,
    "division": "division 1", "team": "team 1", "value": 4,
    "division": "division 1", "team": "team 1", "value": 3,
    "division": "division 1", "team": "team 2", "value": 6,
    "division": "division 1", "team": "team 2", "value": 2,
    "division": "division 1", "team": "team 2", "value": 9,
    "division": "division 1", "team": "team 3", "value": 5,
    "division": "division 1", "team": "team 3", "value": 2,
    "division": "division 1", "team": "team 3", "value": 3,
    "division": "division 2", "team": "team 1", "value": 8,
    "division": "division 2", "team": "team 1", "value": 1,
    "division": "division 2", "team": "team 1", "value": 2,
    "division": "division 2", "team": "team 2", "value": 5,
    "division": "division 2", "team": "team 2", "value": 3,
    "division": "division 2", "team": "team 2", "value": 7,
    "division": "division 2", "team": "team 3", "value": 4,
    "division": "division 2", "team": "team 3", "value": 3,
    "division": "division 2", "team": "team 3", "value": 8,
    ]

    function updateChart(data)

    // Divisions
    var divisions = d3.nest().key(d=>d.division).entries(data);
    var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
    var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);

    // Teams
    var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
    .data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
    var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);

    // Values for each team
    var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
    var barsEnter = bars.enter()
    .append("rect")
    .attr("x", (d,i)=>i*12)
    .attr("width", 10)
    bars.merge(barsEnter)
    .transition()
    .attr("height", d=>d.value*10)

    // Toggle dataset
    currDataset = data == dataset1 ? dataset2 : dataset1;


    let currDataset = dataset1;
    updateChart(currDataset);

    d3.select("#updateData").on("click", () => updateChart(currDataset))

    <p><button id="updateData">Update Data</button>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>









    share|improve this question























      up vote
      1
      down vote

      favorite









      up vote
      1
      down vote

      favorite











      I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).



      d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.



      I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.






      const dataset1 = [
      "division": "division 1", "team": "team 1", "value": 5,
      "division": "division 1", "team": "team 1", "value": 7,
      "division": "division 1", "team": "team 1", "value": 9,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 1", "team": "team 3", "value": 1,
      "division": "division 1", "team": "team 3", "value": 7,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 1", "value": 7,
      "division": "division 2", "team": "team 1", "value": 3,
      "division": "division 2", "team": "team 2", "value": 6,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 2,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 9,
      "division": "division 2", "team": "team 3", "value": 5,
      ]

      const dataset2 = [
      "division": "division 1", "team": "team 1", "value": 1,
      "division": "division 1", "team": "team 1", "value": 4,
      "division": "division 1", "team": "team 1", "value": 3,
      "division": "division 1", "team": "team 2", "value": 6,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 9,
      "division": "division 1", "team": "team 3", "value": 5,
      "division": "division 1", "team": "team 3", "value": 2,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 1", "value": 8,
      "division": "division 2", "team": "team 1", "value": 1,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 2", "value": 5,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 7,
      "division": "division 2", "team": "team 3", "value": 4,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 8,
      ]

      function updateChart(data)

      // Divisions
      var divisions = d3.nest().key(d=>d.division).entries(data);
      var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
      var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);

      // Teams
      var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
      .data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
      var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);

      // Values for each team
      var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
      var barsEnter = bars.enter()
      .append("rect")
      .attr("x", (d,i)=>i*12)
      .attr("width", 10)
      bars.merge(barsEnter)
      .transition()
      .attr("height", d=>d.value*10)

      // Toggle dataset
      currDataset = data == dataset1 ? dataset2 : dataset1;


      let currDataset = dataset1;
      updateChart(currDataset);

      d3.select("#updateData").on("click", () => updateChart(currDataset))

      <p><button id="updateData">Update Data</button>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>









      share|improve this question













      I'm working on a d3 project that has multiple sub-plots with updating data. I have a working example below, but I think I might be over complicating the update() pattern. I didn't include an exit() because the data is consistent (there's always the same amount of divisions, teams and values).



      d3 experts: does this look right to you? What would you modify to make it more performant / maintainable? Should I invoke a function for each component of the chart? e.g. a function for building the division charts, a function for building the team charts, a function for building the bars, etc.



      I'm interested in learning how to write clean maintainable d3 code, so please pass along any resources if you have any.






      const dataset1 = [
      "division": "division 1", "team": "team 1", "value": 5,
      "division": "division 1", "team": "team 1", "value": 7,
      "division": "division 1", "team": "team 1", "value": 9,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 1", "team": "team 3", "value": 1,
      "division": "division 1", "team": "team 3", "value": 7,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 1", "value": 7,
      "division": "division 2", "team": "team 1", "value": 3,
      "division": "division 2", "team": "team 2", "value": 6,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 2,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 9,
      "division": "division 2", "team": "team 3", "value": 5,
      ]

      const dataset2 = [
      "division": "division 1", "team": "team 1", "value": 1,
      "division": "division 1", "team": "team 1", "value": 4,
      "division": "division 1", "team": "team 1", "value": 3,
      "division": "division 1", "team": "team 2", "value": 6,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 9,
      "division": "division 1", "team": "team 3", "value": 5,
      "division": "division 1", "team": "team 3", "value": 2,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 1", "value": 8,
      "division": "division 2", "team": "team 1", "value": 1,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 2", "value": 5,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 7,
      "division": "division 2", "team": "team 3", "value": 4,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 8,
      ]

      function updateChart(data)

      // Divisions
      var divisions = d3.nest().key(d=>d.division).entries(data);
      var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
      var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);

      // Teams
      var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
      .data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
      var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);

      // Values for each team
      var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
      var barsEnter = bars.enter()
      .append("rect")
      .attr("x", (d,i)=>i*12)
      .attr("width", 10)
      bars.merge(barsEnter)
      .transition()
      .attr("height", d=>d.value*10)

      // Toggle dataset
      currDataset = data == dataset1 ? dataset2 : dataset1;


      let currDataset = dataset1;
      updateChart(currDataset);

      d3.select("#updateData").on("click", () => updateChart(currDataset))

      <p><button id="updateData">Update Data</button>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>








      const dataset1 = [
      "division": "division 1", "team": "team 1", "value": 5,
      "division": "division 1", "team": "team 1", "value": 7,
      "division": "division 1", "team": "team 1", "value": 9,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 1", "team": "team 3", "value": 1,
      "division": "division 1", "team": "team 3", "value": 7,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 1", "value": 7,
      "division": "division 2", "team": "team 1", "value": 3,
      "division": "division 2", "team": "team 2", "value": 6,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 2,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 9,
      "division": "division 2", "team": "team 3", "value": 5,
      ]

      const dataset2 = [
      "division": "division 1", "team": "team 1", "value": 1,
      "division": "division 1", "team": "team 1", "value": 4,
      "division": "division 1", "team": "team 1", "value": 3,
      "division": "division 1", "team": "team 2", "value": 6,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 9,
      "division": "division 1", "team": "team 3", "value": 5,
      "division": "division 1", "team": "team 3", "value": 2,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 1", "value": 8,
      "division": "division 2", "team": "team 1", "value": 1,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 2", "value": 5,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 7,
      "division": "division 2", "team": "team 3", "value": 4,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 8,
      ]

      function updateChart(data)

      // Divisions
      var divisions = d3.nest().key(d=>d.division).entries(data);
      var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
      var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);

      // Teams
      var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
      .data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
      var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);

      // Values for each team
      var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
      var barsEnter = bars.enter()
      .append("rect")
      .attr("x", (d,i)=>i*12)
      .attr("width", 10)
      bars.merge(barsEnter)
      .transition()
      .attr("height", d=>d.value*10)

      // Toggle dataset
      currDataset = data == dataset1 ? dataset2 : dataset1;


      let currDataset = dataset1;
      updateChart(currDataset);

      d3.select("#updateData").on("click", () => updateChart(currDataset))

      <p><button id="updateData">Update Data</button>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>





      const dataset1 = [
      "division": "division 1", "team": "team 1", "value": 5,
      "division": "division 1", "team": "team 1", "value": 7,
      "division": "division 1", "team": "team 1", "value": 9,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 2", "value": 1,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 1", "team": "team 3", "value": 1,
      "division": "division 1", "team": "team 3", "value": 7,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 1", "value": 7,
      "division": "division 2", "team": "team 1", "value": 3,
      "division": "division 2", "team": "team 2", "value": 6,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 2,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 9,
      "division": "division 2", "team": "team 3", "value": 5,
      ]

      const dataset2 = [
      "division": "division 1", "team": "team 1", "value": 1,
      "division": "division 1", "team": "team 1", "value": 4,
      "division": "division 1", "team": "team 1", "value": 3,
      "division": "division 1", "team": "team 2", "value": 6,
      "division": "division 1", "team": "team 2", "value": 2,
      "division": "division 1", "team": "team 2", "value": 9,
      "division": "division 1", "team": "team 3", "value": 5,
      "division": "division 1", "team": "team 3", "value": 2,
      "division": "division 1", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 1", "value": 8,
      "division": "division 2", "team": "team 1", "value": 1,
      "division": "division 2", "team": "team 1", "value": 2,
      "division": "division 2", "team": "team 2", "value": 5,
      "division": "division 2", "team": "team 2", "value": 3,
      "division": "division 2", "team": "team 2", "value": 7,
      "division": "division 2", "team": "team 3", "value": 4,
      "division": "division 2", "team": "team 3", "value": 3,
      "division": "division 2", "team": "team 3", "value": 8,
      ]

      function updateChart(data)

      // Divisions
      var divisions = d3.nest().key(d=>d.division).entries(data);
      var divisionCharts = d3.select("body").selectAll("svg").data(divisions, d=>d.key);
      var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", 200).attr("height", 75);

      // Teams
      var teamCharts = divisionCharts.merge(divisionChartsEnter).selectAll("svg")
      .data(d => d3.nest().key(d=>d.team).entries(d.values), d=>d.key)
      var teamChartsEnter = teamCharts.enter().append("svg").attr("x", (d,i) => i*50);

      // Values for each team
      var bars = teamCharts.merge(teamChartsEnter).selectAll("rect").data(d => d.values);
      var barsEnter = bars.enter()
      .append("rect")
      .attr("x", (d,i)=>i*12)
      .attr("width", 10)
      bars.merge(barsEnter)
      .transition()
      .attr("height", d=>d.value*10)

      // Toggle dataset
      currDataset = data == dataset1 ? dataset2 : dataset1;


      let currDataset = dataset1;
      updateChart(currDataset);

      d3.select("#updateData").on("click", () => updateChart(currDataset))

      <p><button id="updateData">Update Data</button>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>








      share|improve this question












      share|improve this question




      share|improve this question








      edited Jul 19 at 3:36









      Stephen Rauch

      3,49951430




      3,49951430









      asked Jul 19 at 2:26









      Jeff Appareti

      82




      82




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          1
          down vote



          accepted










          Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.



          Major issues



          1. Merging the selections



          As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.



          So, they should be something like this (using the first one as example):



           var divisions = d3.nest()
          .key(d => d.division)
          .entries(data);

          var divisionCharts = container.selectAll("svg")
          .data(divisions, d => d.key);

          var divisionChartsEnter = divisionCharts.enter()
          .append("svg")
          .attr("width", width)
          .attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);


          And do the same for the other merged selections.



          2. Appending SVGs inside SVGs



          As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.



          So, instead of SVGs, let's groups (<g>) for each team:



          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest()
          .key(d => d.team)
          .entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter()
          .append("g")
          .attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");

          teamCharts = teamCharts.merge(teamChartsEnter);


          Since you're appending groups you have to translate them, instead of setting the x attribute.



          3. Use scales!



          This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.



          So, create proper scales. In your case:



          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);


          xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.



          4. Bars going up



          Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:



          bars.attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))


          Minor issues




          • Don't append the SVGs directly to the body. Instead of that, select a specific element, like a <div> with container id:



            var container = d3.select("#container");


          • This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.



          • Don't use magic numbers. For instance, define the width and height:



            var width = 200,
            height = 75;


          • Move the toggle logic to outside the update function: that way, you have a function that depends only on the arguments it receives.



          • As a further advice: color the bars by team. It is simple as:



            var color = d3.scaleOrdinal(d3.schemeCategory10);


            And then:



            teamChartsEnter.style("fill", (d)=> color(d.key));


          Demo



          Here is your code with all those changes:






          const dataset1 = [
          "division": "division 1",
          "team": "team 1",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 9
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 5
          ,
          ]

          const dataset2 = [
          "division": "division 1",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 4
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 8
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 5
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 4
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 8
          ,
          ];

          var width = 200,
          height = 75;

          var container = d3.select("#container");

          var color = d3.scaleOrdinal(d3.schemeCategory10);

          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);

          function updateChart(data)

          // Divisions
          var divisions = d3.nest().key(d => d.division).entries(data);
          var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
          var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);

          // Teams
          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
          .style("fill", (d) => color(d.key));

          teamCharts = teamCharts.merge(teamChartsEnter);

          // Values for each team
          var bars = teamCharts.selectAll("rect").data(d => d.values);
          var barsEnter = bars.enter()
          .append("rect")
          .attr("x", (d, i) => xScaleBars(i))
          .attr("width", 10)
          .attr("y", height)
          bars.merge(barsEnter)
          .transition()
          .attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))



          let currDataset = dataset1;
          updateChart(currDataset);

          d3.select("#updateData").on("click", () =>
          currDataset = currDataset === dataset1 ? dataset2 : dataset1;
          updateChart(currDataset);
          )

          <p><button id="updateData">Update Data</button>
          <div id="container"></div>
          <script src="https://d3js.org/d3.v5.min.js"></script>








          share|improve this answer























          • Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
            – Jeff Appareti
            Jul 19 at 22:15











          • It makes very little difference. But for readability I'd do two separate nests.
            – Gerardo Furtado
            Jul 19 at 22:47










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f199793%2fupdate-pattern-for-subplots%23new-answer', 'question_page');

          );

          Post as a guest






























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          1
          down vote



          accepted










          Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.



          Major issues



          1. Merging the selections



          As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.



          So, they should be something like this (using the first one as example):



           var divisions = d3.nest()
          .key(d => d.division)
          .entries(data);

          var divisionCharts = container.selectAll("svg")
          .data(divisions, d => d.key);

          var divisionChartsEnter = divisionCharts.enter()
          .append("svg")
          .attr("width", width)
          .attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);


          And do the same for the other merged selections.



          2. Appending SVGs inside SVGs



          As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.



          So, instead of SVGs, let's groups (<g>) for each team:



          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest()
          .key(d => d.team)
          .entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter()
          .append("g")
          .attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");

          teamCharts = teamCharts.merge(teamChartsEnter);


          Since you're appending groups you have to translate them, instead of setting the x attribute.



          3. Use scales!



          This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.



          So, create proper scales. In your case:



          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);


          xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.



          4. Bars going up



          Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:



          bars.attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))


          Minor issues




          • Don't append the SVGs directly to the body. Instead of that, select a specific element, like a <div> with container id:



            var container = d3.select("#container");


          • This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.



          • Don't use magic numbers. For instance, define the width and height:



            var width = 200,
            height = 75;


          • Move the toggle logic to outside the update function: that way, you have a function that depends only on the arguments it receives.



          • As a further advice: color the bars by team. It is simple as:



            var color = d3.scaleOrdinal(d3.schemeCategory10);


            And then:



            teamChartsEnter.style("fill", (d)=> color(d.key));


          Demo



          Here is your code with all those changes:






          const dataset1 = [
          "division": "division 1",
          "team": "team 1",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 9
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 5
          ,
          ]

          const dataset2 = [
          "division": "division 1",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 4
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 8
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 5
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 4
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 8
          ,
          ];

          var width = 200,
          height = 75;

          var container = d3.select("#container");

          var color = d3.scaleOrdinal(d3.schemeCategory10);

          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);

          function updateChart(data)

          // Divisions
          var divisions = d3.nest().key(d => d.division).entries(data);
          var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
          var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);

          // Teams
          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
          .style("fill", (d) => color(d.key));

          teamCharts = teamCharts.merge(teamChartsEnter);

          // Values for each team
          var bars = teamCharts.selectAll("rect").data(d => d.values);
          var barsEnter = bars.enter()
          .append("rect")
          .attr("x", (d, i) => xScaleBars(i))
          .attr("width", 10)
          .attr("y", height)
          bars.merge(barsEnter)
          .transition()
          .attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))



          let currDataset = dataset1;
          updateChart(currDataset);

          d3.select("#updateData").on("click", () =>
          currDataset = currDataset === dataset1 ? dataset2 : dataset1;
          updateChart(currDataset);
          )

          <p><button id="updateData">Update Data</button>
          <div id="container"></div>
          <script src="https://d3js.org/d3.v5.min.js"></script>








          share|improve this answer























          • Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
            – Jeff Appareti
            Jul 19 at 22:15











          • It makes very little difference. But for readability I'd do two separate nests.
            – Gerardo Furtado
            Jul 19 at 22:47














          up vote
          1
          down vote



          accepted










          Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.



          Major issues



          1. Merging the selections



          As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.



          So, they should be something like this (using the first one as example):



           var divisions = d3.nest()
          .key(d => d.division)
          .entries(data);

          var divisionCharts = container.selectAll("svg")
          .data(divisions, d => d.key);

          var divisionChartsEnter = divisionCharts.enter()
          .append("svg")
          .attr("width", width)
          .attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);


          And do the same for the other merged selections.



          2. Appending SVGs inside SVGs



          As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.



          So, instead of SVGs, let's groups (<g>) for each team:



          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest()
          .key(d => d.team)
          .entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter()
          .append("g")
          .attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");

          teamCharts = teamCharts.merge(teamChartsEnter);


          Since you're appending groups you have to translate them, instead of setting the x attribute.



          3. Use scales!



          This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.



          So, create proper scales. In your case:



          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);


          xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.



          4. Bars going up



          Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:



          bars.attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))


          Minor issues




          • Don't append the SVGs directly to the body. Instead of that, select a specific element, like a <div> with container id:



            var container = d3.select("#container");


          • This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.



          • Don't use magic numbers. For instance, define the width and height:



            var width = 200,
            height = 75;


          • Move the toggle logic to outside the update function: that way, you have a function that depends only on the arguments it receives.



          • As a further advice: color the bars by team. It is simple as:



            var color = d3.scaleOrdinal(d3.schemeCategory10);


            And then:



            teamChartsEnter.style("fill", (d)=> color(d.key));


          Demo



          Here is your code with all those changes:






          const dataset1 = [
          "division": "division 1",
          "team": "team 1",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 9
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 5
          ,
          ]

          const dataset2 = [
          "division": "division 1",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 4
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 8
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 5
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 4
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 8
          ,
          ];

          var width = 200,
          height = 75;

          var container = d3.select("#container");

          var color = d3.scaleOrdinal(d3.schemeCategory10);

          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);

          function updateChart(data)

          // Divisions
          var divisions = d3.nest().key(d => d.division).entries(data);
          var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
          var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);

          // Teams
          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
          .style("fill", (d) => color(d.key));

          teamCharts = teamCharts.merge(teamChartsEnter);

          // Values for each team
          var bars = teamCharts.selectAll("rect").data(d => d.values);
          var barsEnter = bars.enter()
          .append("rect")
          .attr("x", (d, i) => xScaleBars(i))
          .attr("width", 10)
          .attr("y", height)
          bars.merge(barsEnter)
          .transition()
          .attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))



          let currDataset = dataset1;
          updateChart(currDataset);

          d3.select("#updateData").on("click", () =>
          currDataset = currDataset === dataset1 ? dataset2 : dataset1;
          updateChart(currDataset);
          )

          <p><button id="updateData">Update Data</button>
          <div id="container"></div>
          <script src="https://d3js.org/d3.v5.min.js"></script>








          share|improve this answer























          • Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
            – Jeff Appareti
            Jul 19 at 22:15











          • It makes very little difference. But for readability I'd do two separate nests.
            – Gerardo Furtado
            Jul 19 at 22:47












          up vote
          1
          down vote



          accepted







          up vote
          1
          down vote



          accepted






          Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.



          Major issues



          1. Merging the selections



          As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.



          So, they should be something like this (using the first one as example):



           var divisions = d3.nest()
          .key(d => d.division)
          .entries(data);

          var divisionCharts = container.selectAll("svg")
          .data(divisions, d => d.key);

          var divisionChartsEnter = divisionCharts.enter()
          .append("svg")
          .attr("width", width)
          .attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);


          And do the same for the other merged selections.



          2. Appending SVGs inside SVGs



          As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.



          So, instead of SVGs, let's groups (<g>) for each team:



          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest()
          .key(d => d.team)
          .entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter()
          .append("g")
          .attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");

          teamCharts = teamCharts.merge(teamChartsEnter);


          Since you're appending groups you have to translate them, instead of setting the x attribute.



          3. Use scales!



          This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.



          So, create proper scales. In your case:



          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);


          xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.



          4. Bars going up



          Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:



          bars.attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))


          Minor issues




          • Don't append the SVGs directly to the body. Instead of that, select a specific element, like a <div> with container id:



            var container = d3.select("#container");


          • This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.



          • Don't use magic numbers. For instance, define the width and height:



            var width = 200,
            height = 75;


          • Move the toggle logic to outside the update function: that way, you have a function that depends only on the arguments it receives.



          • As a further advice: color the bars by team. It is simple as:



            var color = d3.scaleOrdinal(d3.schemeCategory10);


            And then:



            teamChartsEnter.style("fill", (d)=> color(d.key));


          Demo



          Here is your code with all those changes:






          const dataset1 = [
          "division": "division 1",
          "team": "team 1",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 9
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 5
          ,
          ]

          const dataset2 = [
          "division": "division 1",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 4
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 8
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 5
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 4
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 8
          ,
          ];

          var width = 200,
          height = 75;

          var container = d3.select("#container");

          var color = d3.scaleOrdinal(d3.schemeCategory10);

          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);

          function updateChart(data)

          // Divisions
          var divisions = d3.nest().key(d => d.division).entries(data);
          var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
          var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);

          // Teams
          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
          .style("fill", (d) => color(d.key));

          teamCharts = teamCharts.merge(teamChartsEnter);

          // Values for each team
          var bars = teamCharts.selectAll("rect").data(d => d.values);
          var barsEnter = bars.enter()
          .append("rect")
          .attr("x", (d, i) => xScaleBars(i))
          .attr("width", 10)
          .attr("y", height)
          bars.merge(barsEnter)
          .transition()
          .attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))



          let currDataset = dataset1;
          updateChart(currDataset);

          d3.select("#updateData").on("click", () =>
          currDataset = currDataset === dataset1 ? dataset2 : dataset1;
          updateChart(currDataset);
          )

          <p><button id="updateData">Update Data</button>
          <div id="container"></div>
          <script src="https://d3js.org/d3.v5.min.js"></script>








          share|improve this answer















          Overall, your update function is correct. You're appending several SVGs to the HTML, which is a good and very common approach when creating small multiples. Also, you have a correct (almost, see below) understanding of the update pattern and the merge() method.



          Major issues



          1. Merging the selections



          As I said, you get the principle of merging selections right, but some details are wrong. For instance, you don't need the following selectAll in the merged selection.



          So, they should be something like this (using the first one as example):



           var divisions = d3.nest()
          .key(d => d.division)
          .entries(data);

          var divisionCharts = container.selectAll("svg")
          .data(divisions, d => d.key);

          var divisionChartsEnter = divisionCharts.enter()
          .append("svg")
          .attr("width", width)
          .attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);


          And do the same for the other merged selections.



          2. Appending SVGs inside SVGs



          As stated above, appending multiple SVGs is a very common approach when creating small multiples. However, inside each SVG you're creating nested SVGs, one for each team... not only this is uncommon and unnecessary, but also can lead to some problems. The common approach here is creating groups.



          So, instead of SVGs, let's groups (<g>) for each team:



          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest()
          .key(d => d.team)
          .entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter()
          .append("g")
          .attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)");

          teamCharts = teamCharts.merge(teamChartsEnter);


          Since you're appending groups you have to translate them, instead of setting the x attribute.



          3. Use scales!



          This is a very important advice: use scales in a D3 code. The way you're doing right now (using absolute values and indices for positioning and size) is not correct and, the most important, it will be a hell to debug/maintain. Actually, if you look at the snippets above, you'll see that I'm already using scales on them.



          So, create proper scales. In your case:



          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);


          xScaleTeam is the outer x scale for each small multiple, xScaleBars is the inner x scale (for each team) and yScale is, obviously, the y scale.



          4. Bars going up



          Maybe I'm wrong, but it seems to me that you want the bar chart going up, as almost all bar charts around. That being the case, just do:



          bars.attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))


          Minor issues




          • Don't append the SVGs directly to the body. Instead of that, select a specific element, like a <div> with container id:



            var container = d3.select("#container");


          • This code works just fine if you reference the new v5 version, instead of v4 (which you're using right now). Therefore, change to v5.



          • Don't use magic numbers. For instance, define the width and height:



            var width = 200,
            height = 75;


          • Move the toggle logic to outside the update function: that way, you have a function that depends only on the arguments it receives.



          • As a further advice: color the bars by team. It is simple as:



            var color = d3.scaleOrdinal(d3.schemeCategory10);


            And then:



            teamChartsEnter.style("fill", (d)=> color(d.key));


          Demo



          Here is your code with all those changes:






          const dataset1 = [
          "division": "division 1",
          "team": "team 1",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 9
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 5
          ,
          ]

          const dataset2 = [
          "division": "division 1",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 4
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 8
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 5
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 4
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 8
          ,
          ];

          var width = 200,
          height = 75;

          var container = d3.select("#container");

          var color = d3.scaleOrdinal(d3.schemeCategory10);

          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);

          function updateChart(data)

          // Divisions
          var divisions = d3.nest().key(d => d.division).entries(data);
          var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
          var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);

          // Teams
          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
          .style("fill", (d) => color(d.key));

          teamCharts = teamCharts.merge(teamChartsEnter);

          // Values for each team
          var bars = teamCharts.selectAll("rect").data(d => d.values);
          var barsEnter = bars.enter()
          .append("rect")
          .attr("x", (d, i) => xScaleBars(i))
          .attr("width", 10)
          .attr("y", height)
          bars.merge(barsEnter)
          .transition()
          .attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))



          let currDataset = dataset1;
          updateChart(currDataset);

          d3.select("#updateData").on("click", () =>
          currDataset = currDataset === dataset1 ? dataset2 : dataset1;
          updateChart(currDataset);
          )

          <p><button id="updateData">Update Data</button>
          <div id="container"></div>
          <script src="https://d3js.org/d3.v5.min.js"></script>








          const dataset1 = [
          "division": "division 1",
          "team": "team 1",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 9
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 5
          ,
          ]

          const dataset2 = [
          "division": "division 1",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 4
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 8
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 5
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 4
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 8
          ,
          ];

          var width = 200,
          height = 75;

          var container = d3.select("#container");

          var color = d3.scaleOrdinal(d3.schemeCategory10);

          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);

          function updateChart(data)

          // Divisions
          var divisions = d3.nest().key(d => d.division).entries(data);
          var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
          var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);

          // Teams
          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
          .style("fill", (d) => color(d.key));

          teamCharts = teamCharts.merge(teamChartsEnter);

          // Values for each team
          var bars = teamCharts.selectAll("rect").data(d => d.values);
          var barsEnter = bars.enter()
          .append("rect")
          .attr("x", (d, i) => xScaleBars(i))
          .attr("width", 10)
          .attr("y", height)
          bars.merge(barsEnter)
          .transition()
          .attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))



          let currDataset = dataset1;
          updateChart(currDataset);

          d3.select("#updateData").on("click", () =>
          currDataset = currDataset === dataset1 ? dataset2 : dataset1;
          updateChart(currDataset);
          )

          <p><button id="updateData">Update Data</button>
          <div id="container"></div>
          <script src="https://d3js.org/d3.v5.min.js"></script>





          const dataset1 = [
          "division": "division 1",
          "team": "team 1",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 9
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 5
          ,
          ]

          const dataset2 = [
          "division": "division 1",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 4
          ,

          "division": "division 1",
          "team": "team 1",
          "value": 3
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 6
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 2",
          "value": 9
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 5
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 2
          ,

          "division": "division 1",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 8
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 1
          ,

          "division": "division 2",
          "team": "team 1",
          "value": 2
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 5
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 2",
          "value": 7
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 4
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 3
          ,

          "division": "division 2",
          "team": "team 3",
          "value": 8
          ,
          ];

          var width = 200,
          height = 75;

          var container = d3.select("#container");

          var color = d3.scaleOrdinal(d3.schemeCategory10);

          var xScaleTeam = d3.scaleBand()
          .range([0, width])
          .domain([...new Set(dataset1.map(function(d)
          return d.team
          ))])
          .paddingOuter(0.4);

          var xScaleBars = d3.scaleBand()
          .range([0, xScaleTeam.bandwidth()])
          .domain(d3.range(3))
          .paddingOuter(0.4);

          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max([d3.max(dataset1, function(d)
          return d.value
          ), d3.max(dataset2, function(d)
          return d.value
          )])]);

          function updateChart(data)

          // Divisions
          var divisions = d3.nest().key(d => d.division).entries(data);
          var divisionCharts = container.selectAll("svg").data(divisions, d => d.key);
          var divisionChartsEnter = divisionCharts.enter().append("svg").attr("width", width).attr("height", height);

          divisionCharts = divisionCharts.merge(divisionChartsEnter);

          // Teams
          var teamCharts = divisionCharts.selectAll("g")
          .data(d => d3.nest().key(d => d.team).entries(d.values), d => d.key);

          var teamChartsEnter = teamCharts.enter().append("g").attr("transform", (d) => "translate(" + xScaleTeam(d.key) + ",0)")
          .style("fill", (d) => color(d.key));

          teamCharts = teamCharts.merge(teamChartsEnter);

          // Values for each team
          var bars = teamCharts.selectAll("rect").data(d => d.values);
          var barsEnter = bars.enter()
          .append("rect")
          .attr("x", (d, i) => xScaleBars(i))
          .attr("width", 10)
          .attr("y", height)
          bars.merge(barsEnter)
          .transition()
          .attr("y", d => yScale(d.value))
          .attr("height", d => height - yScale(d.value))



          let currDataset = dataset1;
          updateChart(currDataset);

          d3.select("#updateData").on("click", () =>
          currDataset = currDataset === dataset1 ? dataset2 : dataset1;
          updateChart(currDataset);
          )

          <p><button id="updateData">Update Data</button>
          <div id="container"></div>
          <script src="https://d3js.org/d3.v5.min.js"></script>






          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Jul 21 at 0:10


























          answered Jul 19 at 3:23









          Gerardo Furtado

          1,1342420




          1,1342420











          • Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
            – Jeff Appareti
            Jul 19 at 22:15











          • It makes very little difference. But for readability I'd do two separate nests.
            – Gerardo Furtado
            Jul 19 at 22:47
















          • Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
            – Jeff Appareti
            Jul 19 at 22:15











          • It makes very little difference. But for readability I'd do two separate nests.
            – Gerardo Furtado
            Jul 19 at 22:47















          Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
          – Jeff Appareti
          Jul 19 at 22:15





          Thank you so much! This is very helpful. Glad to know that I'm (mostly) on the right track. I didn't know about the problems with nested svgs so I'll use gs instead. Another question: is there any advantage to nesting the data separately like the sample code? Or would it be better to double nest it like this: var dataNested = d3.nest().key(d => d.division).key(d => d.team)entries(data);
          – Jeff Appareti
          Jul 19 at 22:15













          It makes very little difference. But for readability I'd do two separate nests.
          – Gerardo Furtado
          Jul 19 at 22:47




          It makes very little difference. But for readability I'd do two separate nests.
          – Gerardo Furtado
          Jul 19 at 22:47












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f199793%2fupdate-pattern-for-subplots%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Python Lists

          Aion

          JavaScript Array Iteration Methods