Search code examples
javascripthtmld3.jscss-grid

Control order when adding multiple elements in a d3 join


Follow up from Add Rows to grid layout via d3

With the following snippet I can now add data as rows in a grid layout. However, as you can see, d3 adds first all cell1 then all cell2 and eventually all cell3 divs, messing up the order of my grid.

What would I need to change such that the elements are displayed in row order, i.e.:

<div id="grid">
  <div class="cell1">1/1</div>
  <div class="cell2">1/2</div>
  <div class="cell3">1/3</div>
  <div class="cell1">2/1</div>
  <div class="cell2">2/2</div>
  <div class="cell3">2/3</div>
</div>

function update(data) {
  d3.select('#grid')
    .selectAll('.cell1')
    .data(data, (d) => d.id)
    .join((enter) => {
      enter.append('div')
        .classed('cell1', true)
        .text((d) => d.cell1);
      enter.append('div')
        .classed('cell2', true)
        .text((d) => d.cell2)
      enter.append('div')
        .classed('cell3', true)
        .text((d) => d.cell3);
  })
}

function addRow() {
  const n = data.length + 1;
  const newRow = {cell1: n + '/1', cell2: n + '/2', cell3: n + '/3', id: n};
  data.push(newRow);
  update(data);
}

const data = [
  {cell1: '1/1', cell2: '1/2', cell3: '1/3', id: 1},
  {cell1: '2/1', cell2: '2/2', cell3: '2/3', id: 2}
];

update(data)
#grid {
  display: inline-grid;
  grid-template-columns: repeat(3, 200px);
}

#grid > div:nth-of-type(3n+2) {
  background-color: orange;
}

#grid > div:nth-of-type(3n+1) {
  background-color: purple;
}


#grid > div:nth-of-type(3n+0) {
  background-color: forestgreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<div id="grid">
</div>
<button id="add" onclick="addRow()">Add Row</button>


Solution

  • Besides the beautiful answer from @vals I found another solution, which involves adding a row <div>, which sets display:contents.

    This feels a bit more natural, since the mapping 1 row <-> 1 element (div.row) can be maintained and adding more columns in the future won't require adding new rules to the CSS.

    function update(data) {
      const row = d3.select('#grid')
        .selectAll('.row')
        .data(data, (d) => d.id)
        .join((enter) => {
           const row = enter.append('div')
             .classed('row', true);
           row.append('div')
             .classed('cell1', true)
             .text((d) => d.cell1);
           row.append('div')
             .classed('cell2', true)
             .text((d) => d.cell2)
           row.append('div')
             .classed('cell3', true)
             .text((d) => d.cell3);
       });
    }
    
    function addRow() {
      const n = data.length + 1;
      const newRow = {cell1: n + '/1', cell2: n + '/2', cell3: n + '/3', id: n};
      data.push(newRow);
      update(data);
    }
    
    const data = [
      {cell1: '1/1', cell2: '1/2', cell3: '1/3', id: 1},
      {cell1: '2/1', cell2: '2/2', cell3: '2/3', id: 2}
    ];
    
    update(data)
    #grid {
      display: inline-grid;
      grid-template-columns: repeat(3, 200px);
    }
    
    #grid > .row > div:nth-of-type(3n+2) {
      background-color: orange;
    }
    
    #grid > .row > div:nth-of-type(3n+1) {
      background-color: purple;
    }
    
    #grid > .row > div:nth-of-type(3n+0) {
      background-color: forestgreen;
    }
    
    .row {
      display: contents;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
    <div id="grid">
    </div>
    <button id="add" onclick="addRow()">Add Row</button>