Nathan Hinchey
Nathan Hinchey

Reputation: 1201

d3.js: grouping with <g> (with the data join enter/update/exit cycle)

I made a JSFiddle to explain my problem visually: https://jsfiddle.net/nph/poyxrmn7/

I am trying to use the <g> element effectively in my d3 code to move groups of objects in a way that works with transitions.

As an example, I am trying to make a graph with labeled points. I can do it this way, and it works:

var labeledCircles1 = function(data){
  var svg = d3.select('svg');

  var circles = svg.selectAll('circle')
    .data(data);

  circles.enter().append('circle')
  circles.exit().remove()
  circles
    .attr('cx', function(d,i){ return i; })
    .attr('cy', function(d){ return d; })
    .attr('r', 10)

  var texts = svg.selectAll('text')
    .data(data)

  texts.enter().append('text')
  texts.exit().remove()
  texts
    .text(function(d){ return d; })
    .attr('x', function(d,i){ return i; })
    .attr('y', function(d){ return d; })
    .attr('dy', -10)
};

It outputs something like this:

<svg>
  <circle cx="0" cy="10" r="10"></circle>
  <circle cx="1" cy="30" r="10"></circle>
  <text x="0" y="10" dy="-10">10</text>
  <text x="1" y="30" dy="-10">30</text>
</svg>

But this involves repeating myself a lot, and if I were to add any more elements to each point, I would have to repeat myself again. It also doesn't group the elements logically within the generated svg, as shown above.

I would like to be able to specify the positions only once, and have the groups be more logical. I want code that outputs this:

<svg>
  <g transform="translate(0,10)">
    <circle r="10"></circle>
    <circle r="10"></circle>
  </g>
  <g transform="translate(1,30)">
    <text dy="-10">10</text>
    <text dy="-10">30</text>
  </g>
</svg>

But I can't figure out how to do it. How to start is clear enough:

var svg = d3.select('svg');

var groups = svg.selectAll('.group')
  .data(data)

groups.enter().append('g')
  .attr('class', 'group')
groups.exit().remove();
groups
  .attr('transform', function(d,i){
    var x = i * 20 + 50;
    var y = d + 20;
    return 'translate(' + x + ',' + y + ')';
  })

But then I'm not sure how to proceed from there.

If I append to groups, it works the first time, but when I change the data set it redraws my circle and text elements:

groups.append('circle')
  .attr('r', 10)

groups.append('text')
  .text(function(d) { return d; })
  .attr('dy', -10);

But if I do a selectAll on groups then (of course) it never draws the elements at all.

I'm sure there is something simple I'm missing, but I'm not sure what.

(It seems fairly likely that this is a duplicate, but I was unable to find anyone else addressing this general question.)

Upvotes: 0

Views: 2376

Answers (2)

Le Nhat Anh
Le Nhat Anh

Reputation: 153

If you want to add data one time, the easy way is

    <g>
    <text></text>
    <circle></circle>
</g>
<g>
    <text></text>
    <circle></circle>
</g>
<g>
    <text></text>
    <circle></circle>
</g>
<g>
    <text></text>
    <circle></circle>
</g>
<g>
    <text></text>
    <circle></circle>
</g>

And js function

var labeledCircles3 = function(data) {
    var svg = d3.select('#svg3');
    var groups = svg.selectAll('g').data(data)

    // exit
    groups.exit().remove()

    // new
    var newGroups = groups.enter()
    var newgroup = newGroups.append('g')
    newgroup.append('text')
    newgroup.append('circle')

    // update + new
    groups = newGroups.merge(groups)
    groups.select('text')
        .text(function(d){ return d; })
        .attr('x', function(d,i){ return i * 20 + 50; })
        .attr('y', function(d){ return d + 20; })
        .attr('dy', -10)
    groups.select('circle')
        .attr('cx', function(d,i){ return i * 20 + 50; })
        .attr('cy', function(d){ return d + 20; })
        .attr('r', 10)
}

Upvotes: 1

Eric Guan
Eric Guan

Reputation: 15992

https://jsfiddle.net/guanzo/poyxrmn7/1/

You need to continue performing data joins inside the nested selections. That is, a data join for the circles, and for the text. You were appending a new circle each time, which is why it was being redrawn.

var circles = groups.selectAll('circle')
    .data(d=>[d])

  circles.enter().append('circle')
  circles.attr('r', 10)
        .attr('cx', 0)
        .attr('cy', 0)

Upvotes: 1

Related Questions