Appending multiple non-nested elements for each data member with D3.js

I would like to create multiple non-nested elements using d3 to create a structure like this:

    <div id="parent">
        <p> from data[0] </p>
        <p> from data[0] </p>

        <p> from data[1] </p>
        <p> from data[1] </p>

        <p> from data[2] </p>
        <p> from data[2] </p>
    </div>

creating nested structures would go something like

    d3.select('#parent').selectAll('p').data(data).enter().
           append('p')...append('p')

but I would like to maintain the original selection even after the append, so I could continue appending to the parent element. Thank you!

Answers:

Answer

The idomatic way of doing is with nesting:

var divs = d3.select('#parent').selectAll('p').data(data).enter().append('div');

divs.append('p')
divs.append('p')

Which creates:

<div id="parent">
  <div>
    <p> from data[0] </p>
    <p> from data[0] </p>
  </div>

  <div>
    <p> from data[1] </p>
    <p> from data[1] </p>
  </div>

  <div>
    <p> from data[2] </p>
    <p> from data[2] </p>
  </div>
</div>

If that won't work, save your selection and repeatedly append:

var enterSelection = d3.select('#parent').selectAll('p').data(data).enter();

enterSelection.append('p')
enterSelection.append('p')

then sort what you've added:

d3.select('#parent').selectAll('p').sort(function(a, b){ return a.index - b.index; })

You'll need to add an index property to each element of data that describes the sort order. The normal i is only defined in the context of a particular selection, which is lost when we reselect.

Answer

Use append() for the first item and insert() for the second. This eliminates any need to sort afterwards (thanks to @scuerda's comment for pointing this out) (JSFiddle):

data = [{}, {}, {}];
var enterSelection = d3.select('#parent').selectAll('p').data(data).enter()

enterSelection.append('p').text(function(d, i) {return 'from data[' + i + ']'})
enterSelection.insert('p').text(function(d, i) {return 'from data[' + i + ']'})

This will give you the exact structure requested:

<p>from data[0]</p>

<p>from data[0]</p>

<p>from data[1]</p>

<p>from data[1]</p>

<p>from data[2]</p>

<p>from data[2]</p>
Answer

You can also do this in a single select/enter cycle as follows

d3.select('#parent').selectAll('p').data(data).enter().
append('p').text(function(d) {return 'from data[0]')}).
select(function() { return this.parentNode; }).
append('p').text(function(d) {return 'from data[0]')});
Answer

Instead of a .append(),

You can also wrap a function that creates new content in a .html()

d3.select('#parent')
  .selectAll('div')
    .data(data)
  .enter()
    .append('div')
    .html(function(d) {return "<p>" + from data[0] + "<p>" etc..... ;});
Answer

Similar to the above but in a different idiom. This is truer to the nested selections approach and eliminates the need for sorting or insertion:

var divs = d3.select('#parent');

var ps = divs.selectAll('#parent > div')
    .data(d3.range(3)).enter().append('div');

ps.append('p').html(function(d,i) { return 'from data[' + i + ']'; });
ps.append('p').html(function(d,i) { return 'from data[' + i + ']'; });

More elegantly (and probably faster):

var divs = d3.select('#parent');

  var ps = divs.selectAll('#parent > div')
    .data(d3.range(3)).enter().append('div');

    ps.append('p').html(function(d,i) { return 'from data[' + i + ']'; })
    .select(function() { return this.parentNode.appendChild(this.cloneNode(true)); });

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.