1

This example works fine but if I change below line to arrow function, it won't work anymore:

Good version:

.each(function() {g.append(() => this)})

Error arrow function version:

.each(d => {g.append(() => this)})

How to understand this two line's difference?

tree_demo()
function tree_demo() {
  var root = {
    name: '/',
    contents: [
      {
        name: 'AAAA',
        contents: [
          { name: 'BBBB' },
          { name: 'CCCCCCCCCCCC' },
          { name: 'DDDD' },
          { name: 'EEEE' },
        ],
      },
    ],
  };

  var width = 300
  var height = 200
  var margin = 20
  var radius = 5
  var treemap = d3.tree().size([height-margin*2,width-margin*2-70])
  var nodes = treemap(d3.hierarchy(root,d => d.contents))
  var links = treemap(nodes).links();

  var layoutRoot = d3
  .select("body")
  .append('svg:svg')
  .attr('width', width)
  .attr('height', height)
  .append('svg:g')
  .attr('class', 'container')
  .attr('transform', 'translate(' + margin + ',' + margin+')');

  var diagonal = function link(d) {
    var sx = d.source.y
    var sy = d.source.x
    var tx = d.target.y
    var ty = d.target.x

    return ['M',sx,sy,'C',(sx+tx)/2,sy,
            (sx+tx)/2,ty,tx,ty].join(' ')
  };

  layoutRoot
    .selectAll('path.link')
    .data(links)
    .enter()
    .append('path')
    .attr('class', 'link')
    .attr('d', diagonal)
    .attr('fill','none')
    .attr('stroke','black')

  var nodeGroup = layoutRoot
  .selectAll('g.node')
  .data(nodes)
  .enter()
  .append('g')
  .attr('class', 'node')
  .attr('transform', function(d) {
    return 'translate(' + d.y + ',' + d.x + ')';
  });

  nodeGroup
    .append('circle')
    .attr('class', 'node-dot')
    .attr('r', radius)
    .attr('stroke','steelblue')
    .attr('stroke-width',2)
    .attr('fill','none')

  nodeGroup
    .append('text')
    .attr('text-anchor', function(d) {
    return d.children ? 'end' : 'start';
  })
    .attr('dx', function(d) {
    var gap = 2 * radius;
    return d.children ? -gap : gap;
  })
    .attr('dy', 3)
    .text(function(d) {
    return d.data.name;
  });

  var g = layoutRoot.append('g')
  nodeGroup.filter((d,i) => i == 2 || i==3)
  .each(function() {g.append(() => this)}) //work version
  //.each(d => {g.append(() => this)}) //won't work

  var border = 5
  var bbox = g.node().getBBox()
  layoutRoot.insert('rect','path.link')
    .attr('stroke','black')
    .attr('fill','steelblue')
    .attr('fill-opacity',0.2)
    .attr('x',bbox.x-border)
    .attr('y',bbox.y-border)
    .attr('rx',8)
    .attr('ry',8)
    .attr('width',bbox.width + 2*border)
    .attr('height',bbox.height + 2*border)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
beetlej
  • 1,841
  • 4
  • 13
  • 27
  • Arrow functions have a different `this` context than those functions defined with `function`. See [here](https://stackoverflow.com/a/34361380/7106086)(SO - "Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?") or [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)(MDN). – Andrew Reid Jun 23 '21 at 21:28
  • And [here](https://stackoverflow.com/a/45262284/7106086) is a D3 specific example case. – Andrew Reid Jun 23 '21 at 21:34

1 Answers1

2

In general, you can substitute the anonymous function() {...} with the ES6 arrow function () => except you are using this.

In the first version

.each(function() {g.append(() => this)})

this refers to the current DOM element. Whereas in the arrow function

.each(d => {g.append(() => this)})

this refers to the enclosing context.

If you want to refer to the current DOM element in an arrow function you have to do the following:

.each((d, i, nodes) => {g.append(() => nodes[i])})

Here is the full code:

tree_demo()
function tree_demo() {
  var root = {
    name: '/',
    contents: [
      {
        name: 'AAAA',
        contents: [
          { name: 'BBBB' },
          { name: 'CCCCCCCCCCCC' },
          { name: 'DDDD' },
          { name: 'EEEE' },
        ],
      },
    ],
  };

  var width = 300
  var height = 200
  var margin = 20
  var radius = 5
  var treemap = d3.tree().size([height-margin*2,width-margin*2-70])
  var nodes = treemap(d3.hierarchy(root,d => d.contents))
  var links = treemap(nodes).links();

  var layoutRoot = d3
  .select("body")
  .append('svg:svg')
  .attr('width', width)
  .attr('height', height)
  .append('svg:g')
  .attr('class', 'container')
  .attr('transform', 'translate(' + margin + ',' + margin+')');

  var diagonal = function link(d) {
    var sx = d.source.y
    var sy = d.source.x
    var tx = d.target.y
    var ty = d.target.x

    return ['M',sx,sy,'C',(sx+tx)/2,sy,
            (sx+tx)/2,ty,tx,ty].join(' ')
  };

  layoutRoot
    .selectAll('path.link')
    .data(links)
    .enter()
    .append('path')
    .attr('class', 'link')
    .attr('d', diagonal)
    .attr('fill','none')
    .attr('stroke','black')

  var nodeGroup = layoutRoot
  .selectAll('g.node')
  .data(nodes)
  .enter()
  .append('g')
  .attr('class', 'node')
  .attr('transform', function(d) {
    return 'translate(' + d.y + ',' + d.x + ')';
  });

  nodeGroup
    .append('circle')
    .attr('class', 'node-dot')
    .attr('r', radius)
    .attr('stroke','steelblue')
    .attr('stroke-width',2)
    .attr('fill','none')

  nodeGroup
    .append('text')
    .attr('text-anchor', function(d) {
    return d.children ? 'end' : 'start';
  })
    .attr('dx', function(d) {
    var gap = 2 * radius;
    return d.children ? -gap : gap;
  })
    .attr('dy', 3)
    .text(function(d) {
    return d.data.name;
  });

  var g = layoutRoot.append('g')
  nodeGroup.filter((d,i) => i == 2 || i==3)
  // .each(function() {g.append(() => this)}) //work version
  // .each(d => {g.append(() => this)}) //won't work
  .each((d, i, nodes) => {g.append(() => nodes[i])}) //works

  var border = 5
  var bbox = g.node().getBBox()
  layoutRoot.insert('rect','path.link')
    .attr('stroke','black')
    .attr('fill','steelblue')
    .attr('fill-opacity',0.2)
    .attr('x',bbox.x-border)
    .attr('y',bbox.y-border)
    .attr('rx',8)
    .attr('ry',8)
    .attr('width',bbox.width + 2*border)
    .attr('height',bbox.height + 2*border)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
ee2Dev
  • 1,938
  • 20
  • 29
  • Great answer, how to understand "() => nodes[i]", what's the difference with "nodes[i]"? – beetlej Jun 23 '21 at 21:40
  • I try to append it directly "g.append(n[i])", but it doesn't work! – beetlej Jun 23 '21 at 21:53
  • n(i) is not known/undefined. A function within g.append is a callback function where you get 3 arguments passed, in the order: the current datum -d-, the current index -i- and the current group -nodes. – ee2Dev Jun 23 '21 at 21:57
  • I meant: a function within .each() is a callback function... – ee2Dev Jun 23 '21 at 22:04
  • selection.append() takes a string (= the element) or a function (returning the element) as an argument https://github.com/d3/d3-selection#selection_append – ee2Dev Jun 24 '21 at 17:28