4

I have a simple array with objects for my data and I generate divs from it. Instead of creating only one div for each data element, I would like to create several divs, depends on the number that appears in the data (as one of the object's properties).

For example in case that the "num" for a data element is 4, it will generate 4 divs.

Here is the code I have for this part:

data = [
  {num: 4, category: 'A'},
  {num: 3, category: 'B'},  
  {num: 2, category: 'D'},  
  {num: 5, category: 'A'}    
]

d3.select('body')
  .selectAll('div')
  .data(data)
  .enter()
  .append('div')
  .text((d)=> d.num)
  .style('width', (d) => d.num * 20 + "px")

I've tried to solve it with a for loop but I'm not sure how to loop in the middle of a d3 selection, while still having access to the data.

Any idea would be very much appreciated!

Vered R
  • 45
  • 8
  • Just join the `num` data to child `div` elements and `enter` those? – Ryan Morton Apr 02 '18 at 17:11
  • Possibly useful for you: https://stackoverflow.com/questions/21485981/appending-multiple-non-nested-elements-for-each-data-member-with-d3-js – Josh Rumbut Apr 02 '18 at 17:35
  • Thanks for the comment! I'm very new to D3 so I'm not sure if I fully understand what you mean. Do I need to select the child div and then enter the data again? How it will generate multiple elements? – Vered R Apr 02 '18 at 17:38
  • I saw this question earlier but I'm not sure how I can have access to the data, because I want to generate x numbers of divs, based on the number in each data element, if that makes sense... – Vered R Apr 02 '18 at 17:40
  • Something you should consider is changing the data structure you're using to have one entry for every element you want created. While there are probably a number of workarounds possible, d3 is easiest to use when you have a 1 to 1 mapping between data points and elements. – Josh Rumbut Apr 02 '18 at 17:40
  • Thanks. I'll see if there is a way to generate the data itself differently. – Vered R Apr 02 '18 at 17:43
  • `data.map((i) => (new Array(i.num)).fill(i.category, 0, i.num)).reduce((acc, i) => acc.concat(i))` this little chain can flatten out the array, not sure if this is exactly what you need but hopefully it can give you a start. – Josh Rumbut Apr 02 '18 at 17:51
  • Thanks Josh! I'll play with this code and see if it can help to solve it. – Vered R Apr 02 '18 at 18:30

1 Answers1

2

Here's how I'd do it:

<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>
  <script>
    data = [{
      num: 4,
      category: 'A'
    }, {
      num: 3,
      category: 'B'
    }, {
      num: 2,
      category: 'D'
    }, {
      num: 5,
      category: 'A'
    }]

    d3.select('body')
      .selectAll('div')
      .data(data) // bind our data
      .enter()
      // inner selection
      .selectAll('div')
      // inner selection data binding
      // creates array of repeating datum that is length of num
      .data((d) =>
        d3.range(d.num).map(() => d)
      ) 
      .enter()
      .append('div')
      .text((d) => d.num)
      .style('width', (d) => d.num * 20 + "px");
  </script>
</body>

</html>
Mark
  • 106,305
  • 20
  • 172
  • 230