Scott
Scott

Reputation: 3344

d3 data binding not creating child elements using join

I am thoroughly stumped and would love a helping hand from anyone who can kick me in the right direction. I'm trying to create groups g and a single rectangle inside them. The join works great for the g parent but it does not create the children. What am I missing? (I'm thinking in joins!)

I've tried replacing the join with an enter().append('g') to no avail either, so I'm missing something.

Here is a jsfiddle.

var svg = d3.select("div#canvas")
                    .append("svg")
                    .attr("width", '100%')
                    .attr("height", '100%');

let NodeData = [
    {
        'x': 20,
        'y': 20,
        'id': 'abc',
        'fill': '#D4C2F1'
    },
    {
        'x': 20,
        'y': 80,
        'id': 'def',
        'fill': '#B3D2C5'
    },
];

function updateNodes(data) {

    var groups = svg.selectAll('g').data(data)
                    .join('g')
                        .attr('node-id', function (d) { return d.id; })
                        .attr('transform', function (d) { return `translate(${d.x}, ${d.y})` });

    var rects = groups.selectAll('rect')
                    .data(function (d) { return d; })
                    .join('rect')
                        .attr('x', 0)
                        .attr('y', 0)
                        .attr('width', 80)
                        .attr('height', 20)
                        .attr('stroke', '#666666')
                        .attr('fill', function (d) { return d.fill; });
}

updateNodes(NodeData);

Upvotes: 2

Views: 869

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38181

selection.data() requires an array (or function that returns an array). You are not passing an array to .data() when trying to create the child rects. You are passing an object - an individual item in the original data array, so no elements are entered.

To fix this you can simply use:

var rects = groups.selectAll('rect')
                .data(function (d) { return [d]; })

Updated fiddle,

But, this is not the best approach if you are just passing the parent's datum as is to a single child. You don't need to use a nested enter/update/exit cycle, you can just append to the parents:

var groups = svg.selectAll('g').data(data)
   .join('g')
   .attr('node-id', function (d) { return d.id; })
   .attr('transform', function (d) { return `translate(${d.x}, ${d.y})` });

var rects = groups.append("rect")
   .attr('x', 0)
   .attr('y', 0)
   .attr('width', 80)
   .attr('height', 20)
   .attr('stroke', '#666666')
   .attr('fill', function (d) { return d.fill; });

Modified fiddle

The new child elements (rects) inherit the bound data of their parents, as you can see here:

var svg = d3.select("div#canvas")
  .append("svg")
  .attr("width", '100%')
  .attr("height", '100%');

let NodeData = [
    {
        'x': 20,
        'y': 20,
        'id': 'abc',
        'fill': '#D4C2F1'
    },
    {
        'x': 20,
        'y': 80,
        'id': 'def',
        'fill': '#B3D2C5'
    },
];

function updateNodes(data) {

  var groups = svg.selectAll('g').data(data)
     .join('g')
     .attr('node-id', function (d) { return d.id; })
     .attr('transform', function (d) { return `translate(${d.x}, ${d.y})` });

  var rects = groups.append('rect')
     .attr('x', 0)
     .attr('y', 0)
     .attr('width', 80)
     .attr('height', 20)
     .attr('stroke', '#666666')
     .attr('fill', function (d) { return d.fill; });
     
  rects.each(function(d) {
    console.log(d);
  })
}

updateNodes(NodeData);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.0/d3.min.js"></script>
<div id="canvas" style="border: 1px solid #D9D9D9; width: 100%; height: 600px; margin-top: 6px"></div>

Upvotes: 5

Related Questions