nachocab
nachocab

Reputation: 14374

how to nest selections in d3?

I want to draw three contiguous squares with different colors (that's no problem, it's in the "scale" variable) and put a variable number of smaller squares underneath each big color (that, I can't do, the "dots" variable). What am I doing wrong? This is my code, and the gist https://gist.github.com/3013836:

var data = [-1,0,1]

var rect_size = 25; //px

var x = d3.scale.ordinal().domain(d3.range(data.length)).rangeBands([0, rect_size*data.length]);
var color = d3.scale.linear().domain([-1.5, 0, 1.5]).range(["#278DD6","#ffffff","#d62728"]).clamp(false);

var svg = d3.select("body").append("svg")
    .attr("width", 800)
    .attr("height", 200)

scale = svg.selectAll("rect")
    .data(data)
  .enter().append("rect")
    .attr("x", function(d,i) { return x(i); })
    .attr("y",0)
    .attr("width", rect_size)
    .attr("height", rect_size)
    .style("fill", function(d) { return color(d); });

dots = [[1,2,3],[1,2],[1,2,3,4]]
var y = d3.scale.ordinal().domain(d3.range(dots.length)).rangeBands([0, (rect_size/2)*dots.length]);
scale.selectAll("g")
    .data(dots, function(d){return d;})
  .enter().append("rect")
    .attr("x", function(d,i) {return x(i);})
    .attr("y", function(d,i) {return y(i);})
    .attr("width", rect_size/2)
    .attr("height", rect_size/2)
    .style("fill", "black");

Upvotes: 0

Views: 713

Answers (1)

mbostock
mbostock

Reputation: 51819

You've defined the selection scale as three rect elements. Then, you've added three more rect elements within each of those. The resulting structure, if you use your browser's element inspector, will look like this:

<rect class="outer">
  <rect class="inner"></rect>
  <rect class="inner"></rect>
  <rect class="inner"></rect>
</rect>

This only displays three rects because rects are not container elements in SVG; the inner rects are ignored.

If you want to group elements, you need a g (group) element. You want a structure like this:

<g>
  <rect class="outer"></rect>
  <rect class="inner"></rect>
  <rect class="inner"></rect>
  <rect class="inner"></rect>
</g>

You can do this by first creating a selection of g elements using a data-join. Then append a single outer rect to the g elements. And lastly use a nested join to create the inner rects. Something like:

var g = svg.selectAll("g")
    .data(outerData)
  .enter().append("g");

var outerRect = g.append("rect")
    .attr("class", "outer");

var innerRect = g.selectAll(".inner")
    .data(innerData)
  .enter().append("rect")
    .attr("class", "inner");

A few other things:

  • You can use a transform on the g elements so that you don't have to position the outer and inner rects separately. This way, the inner rects can be positioned relative to the outer rects and the code is simpler.

  • Your last join is broken because you're selecting "g" and appending "rect"; whatever you append should match your selector.

  • You generally don't need a key function with your data join unless you're also handling update and exit. Since you're only handling enter in your join, and you know the selection is empty, the key function is unnecessary.

Upvotes: 1

Related Questions