Scoping in r2d3 visualization - d3.selectAll vs svg.selectAll

180 Views Asked by At

Why does d3.selectAll('rect')... not work as seen in the script below in the mouseover function, but svg.selectAll('rect')... does?

svg is the special pre-set selector from r2d3.

Additionally, I have noticed that running e.g. d3.selectAll('rect').remove() from the browser console does not work when running the visualization from Rstudio.

This is from the r2d3 example, as sample.js:

// !preview r2d3 data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20)

var barHeight = Math.floor(height / data.length);

svg.selectAll('rect')
  .data(data)
  .enter().append('rect')
    .attr('width', function(d) { return d * width; })
    .attr('height', barHeight)
    .attr('y', function(d, i) { return i * barHeight; })
    .attr('fill', 'steelblue')
    .on('mouseover', function(d){
      d3.select(this).attr('fill', 'red')
      
      //svg.selectAll('rect').attr('fill', 'red')
      d3.selectAll('rect').attr('fill', 'red')
      
    })
    

Run from R via r2d3::r2d3("sample.js", data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20))

2

There are 2 best solutions below

0
Andrew Reid On BEST ANSWER

By default r2d3 places the visualization in a shadow DOM. Both d3.select and d3.selectAll start their search at the root element of the DOM, but do not venture into a shadow DOM's child nodes.

Neither select nor selectAll will cross into a shadow tree from the current tree being searched. As svg is a selection of an element within the shadow DOM, the rectangles can be found with svg.selectAll("rect") but not d3.selectAll("rect") (the rects aren't "shadowed" relative to the SVG).

The easiest solution is to not create a shadow DOM by setting the r2d3 shadow option to false.

eg (using the base example from r2d3's documentation):

r2d3(options(r2d3.shadow = FALSE), data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20), script = "barchart.js")

Of course in doing so you lose the encapsulation offered by the shadow root, but this might be preferable or neutral depending on the situation.

0
Kyouma On

I've found that sometimes the r2d3.shadow option doesn't always work well, so here's another possible solution. The parent of the event target will be the svg/g that you've placed it in.

// !preview r2d3 data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20)

var barHeight = Math.floor(height / data.length);

svg.selectAll('rect')
  .data(data)
  .enter().append('rect')
    .attr('width', function(d) { return d * width; })
    .attr('height', barHeight)
    .attr('y', function(d, i) { return i * barHeight; })
    .attr('fill', 'steelblue')
    .on('mouseover', function(d){
      const parent = d3.select(d.target.parentElement); // this is the original svg
      
      
      //svg.selectAll('rect').attr('fill', 'red')
      parent.selectAll('rect').attr('fill', 'blue')
      d3.select(this).attr('fill', 'red')
      
    })