sophos
sophos

Reputation: 107

How to append multiple rectangles in D3?

I´m wondering if it is possible to append multiple rectangles per datapoint. In my example there are three datapoints. For each I try to create 2 rectangles. In the end there have to be 6 rectangles. 3 in red, 3 in blue. IMAGE

According to this answer, I tried the following solution:

var svg = d3.select("body").append("svg");

      svg.selectAll("rect")
      .data([10,60,120])
      .enter()
      .append("g")
      .append("rect")
      .attr("width", 20)
      .attr("height", 20)
      .attr("x", 20)
      .attr("y", function(d) {return d})
      .attr("fill",  "red")

      .selectAll("rect")
      .data(function(d) { return d3.range(d); })
      .enter()
      .append("rect")
      .attr("width", 20)
      .attr("height", 20)
      .attr("x", 60)
      .attr("y", function(d) {return d})
      .attr("fill",  "blue");

Unfortunately the blue rectangles are created inside the red ones. Any idea how to achieve this? Here is an EXAMPLE

Upvotes: 5

Views: 8568

Answers (1)

i alarmed alien
i alarmed alien

Reputation: 9520

You're successfully creating extra rectangles, but unfortunately they are all nested inside the first three rectangles. If you have a web developer extension on your browser, select one of the rectangles and then view the source to see.

If you want to append the rectangles, rather than nest them inside the original rectangle, you need to append both rectangles to your g node. When you bind the data and add your g nodes, assign those nodes to a variable:

var svg = d3.select("body").append("svg");

var nodes = svg.selectAll(".rect")
  .data([10,60,120])
  .enter()
  .append("g")
  .classed('rect', true)

Then you can append the two rectangles to the g nodes:

// red rectangles
nodes.append("rect")
  .attr("width", 20)
  .attr("height", 20)
  .attr("x", 20)
  .attr("y", function(d) {return d})
  .attr("fill",  "red")

// blue ones
nodes.append("rect")
  .attr("width", 20)
  .attr("height", 20)
  .attr("x", 120)
  .attr("y", function(d) {return d})
  .attr("fill",  "blue")

Note that if you do this:

var nodes = svg.selectAll("rect")
  .data([10,60,120])
  .enter()
  .append("g")
  .append("rect")
  .attr("width", 20)
  .attr("height", 20)
  .attr("x", 20)
  .attr("y", function(d) {return d})
  .attr("fill",  "red")

nodes will be a reference to the rect elements, as they were the last thing added, and anything appended to nodes will be added inside the rect elements.

Note also that you only need to bind the data once, to the g elements; it is automatically inherited by all the child nodes of those g elements.

Here's the completed example:

var svg = d3.select("body").append("svg");

var g = svg.selectAll(".rect")
  .data([10,60,120])
  .enter()
  .append("g")
  .classed('rect', true)

g.append("rect")
  .attr("width", 20)
  .attr("height", 20)
  .attr("x", 20)
  .attr("y", function(d) {return d})
  .attr("fill",  "red")

g.append("rect")
  .attr("width", 20)
  .attr("height", 20)
  .attr("x", 120)
  .attr("y", function(d) {return d})
  .attr("fill",  "blue")
<script src="https://d3js.org/d3.v5.js"></script>

Upvotes: 16

Related Questions