简体   繁体   中英

curiosity on d3's selectAll method

I am trying to understand how d3's methods work. I think I've been quite getting d3, basic level of course, but there is one weird thing about selectAll method I don't understand. So when I try to create and append dom nodes to selected dom element, no matter it exists or not, sometimes it creates four nodes or two, or six on the other cases. To make the question clear, I am going to use simple examples.

HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>
  <body>
     <svg></svg>
  </body>
</html>

JS:

    dummyData = [
         {
           name: 'A',
           age: 50
         },
         {
           name: 'B',
           age: 20
         }
       ]

    svg = d3.select('svg')
            .attr('width','500')
            .attr('height', '300')
          .append('g')
            .attr('transform','translate(40, 40)');


function example_1() {

    svg.selectAll('circle')
      .data(dummyData)
      .enter()
    .append('circle')
      .attr('transform', d => `translate(${d.age}, 20)`)
      .attr('cx',32).attr('cy',53)
      .attr('r',15);

 }


function example_2() {

    svg.selectAll('g')
      .data(dummyData)
      .enter()
    .append('circle')
      .attr('transform', d => `translate(${d.age}, 20)`)
      .attr('cx',32)
      .attr('cy',53)
      .attr('r',15);
}

function example_3() {

    svg.selectAll('div')  // This div is an arbitrary value. It can be any html tag to output the same result 
      .data(dummyData)
      .enter()
    .append('circle')
      .attr('transform', d => `translate(${d.age}, 20)`)
      .attr('cx',32)
      .attr('cy',53)
      .attr('r',15);

 }

function example_4() {

   svg.selectAll('g')
      .data(dummyData)
      .enter()
    .append('circle')
      .attr('transform', d => `translate(${d.age}, 20)`)
      .attr('cx',32)
      .attr('cy',53)
      .attr('r',15);

    svg.selectAll('g')
      .data(dummyData)
      .enter()
    .append('path')
      .attr('stroke','#000')
      .attr('d',`M5,5H500`)
}

function example_5() {

   svg.selectAll('g')
      .data(dummyData)
      .enter()
    .append('circle')
      .attr('transform', d => `translate(${d.age}, 20)`)
      .attr('cx',32)
      .attr('cy',53)
      .attr('r',15);

    svg.selectAll('g')
      .data(dummyData)
      .enter()
    .append('g')
    .append('path')
      .attr('stroke','#000')
      .attr('d',`M5,5H500`)
}


example_1(); // This one creates DOM as: 

<svg>
  <g>
    <circle></circle>
    <circle></circle>
  </g>
</svg>

example_2(); // This one creates DOM as:

<svg>
  <g>
    <circle></circle>
    <circle></circle>
    <circle></circle>
    <circle></circle>
  </g>
</svg>

example_3(); // This one creates DOM as:

<svg>
  <g>
    <circle></circle>
    <circle></circle>
    <circle></circle>
    <circle></circle>
  </g>
</svg>

example_4(); // This one creates DOM as:

<svg>
  <g>
    <circle></circle>
    <circle></circle>
    <path></path>
    <path></path>
    <circle></circle>
    <circle></circle>
    <path></path>
    <path></path>
  </g>
</svg>

example_5(); // This one creates DOM as:

<svg>
  <g>
    <circle></circle>
    <circle></circle>
    <g>
      <path></path>
    </g>
    <g>
      <path></path>
    </g>
  </g>
</svg>

The variable, svg, is a dom node g as a child of svg in my example. Which means it does have neither a circle nor g nor div as its child. Then what is the selectAll method is used for ? Why can we not just write as

svg.data(dummyData)
  .enter()
.append('circle')
  .attr('transform', d => `translate(${d.age}, 20)`)
  .attr('cx',32).attr('cy',53)
  .attr('r',15);

Although I have tried with more different examples, it all behaves differently, but I really can't see what's happening behind the scene. Please help me to understand at least a little. I am very confused.

Let's answer your last question first. Why can't you:

svg.data(dummyData).enter()

d3 is using an enter, update, exit pattern. There's tons of reading out there on the pattern and I highly recommend you study up on it. But the quickie is this, it's not just about creating the initial elements but also updating them later. .selectAll is find me all the elements matching my selector. .data is bind the data to them. .enter is tell me all the data elements that were not in my .selectAll . So, the answer to your question becomes, without a .selectAll , there's no way for d3 to calculate which data is entering and nothing is appended. In the classic usage, on an initial rendering, the .selectAll returns empty (none of them exist yet), so they are all entering.

Now, with the basics, let's follow your example 5:

svg.selectAll('g') //<-- no gs exist in svg
  .data(dummyData)
  .enter() //<-- so, since data is two elements
.append('circle') //<-- you get two circles
  .attr('transform', d => `translate(${d.age}, 20)`)
  .attr('cx',32)
  .attr('cy',53)
  .attr('r',15);

svg.selectAll('g') //<-- no gs still exist in svg
  .data(dummyData)
  .enter() //<-- so since data is two elements
.append('g') //<-- you get two gs
.append('path') //<-- each with a path under them
  .attr('stroke','#000')
  .attr('d',`M5,5H500`)

Now, what you are doing doesn't really make sense here. Why select g if you care about circles? Usually, I wouldn't even recommend selecting elements at all. If I want a circle to represent each of my datum , I would do something like:

svg.selectAll('.my_cool_circle')
  .data(dummyData)
  .enter()
.append('circle')
.attr('class', 'my_cool_circle')
...

By using a class, I know that those circles are the circles represent my dummyData . Also, if I need to come back later an update my circles (because my data is now:

dummyData = [
     {
       name: 'A',
       age: 50
     },
     {
       name: 'B',
       age: 20
     },
     {
       name: 'C',
       age: 30
     }          
   ]

).

Then:

 svg.selectAll('.my_cool_circle')
  .data(dummyData)
  .enter()

Would only return the 'C' datum and I'd only append 1 new circle.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM