Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 append() with function argument

This works:

// A
d3.select("body").selectAll(".testDiv")
  .data(["div1", "div2", "div3"])
  .enter().append("div")
    .classed("testDiv", true)
    .text(function(d) { return d; });

The following snippet is identical except that the argument for append, instead of being "div" as above, is a function(d) that simply returns "div":

// B
d3.select("body").selectAll(".testDiv")
  .data(["div1", "div2", "div3"])
  .enter().append(function(d) { return "div"; })
    .classed("testDiv", true)
   .text(function(d) { return d; });

B, however, does not work, and instead returns the error message "Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'."

How is "div" as an argument for append() different from function(d) { return "div"; }?

like image 255
Steve Chall Avatar asked Feb 12 '15 18:02

Steve Chall


2 Answers

The short answer is if you are giving append a function as an argument the function must return a DOM element. The documentation of the append method states:

The name may be specified either as a constant string or as a function that returns the DOM element to append.

The following is a valid use of append with a function as an argument:

.append(function() { return document.createElement('div');});

As the code below does not return a DOM element it would be considered invalid.

.append(function() { return 'div';});

The reason for this may be seen in the source code:

  d3_selectionPrototype.append = function(name) {
    name = d3_selection_creator(name);
    return this.select(function() {
      return this.appendChild(name.apply(this, arguments));
    });
  };

  function d3_selection_creator(name) {
    function create() {
      var document = this.ownerDocument, namespace = this.namespaceURI;
      return namespace ? document.createElementNS(namespace, name) : document.createElement(name);
    }
    function createNS() {
      return this.ownerDocument.createElementNS(name.space, name.local);
    }
    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
  }

As you can see if typeof name === "function" (near the bottom) is true the create or createNS functions are never called. As appendChild only accepts a DOM element the function given to append must be a DOM element.

like image 172
DWB Avatar answered Sep 21 '22 21:09

DWB


If the purpose for adding a function within the .append() is to have a conditional statement, then you have to make sure to have a return for every case.

You can't choose to append a div only sometimes. A DOM element has to always be returned otherwise you will get the same error.

For example this will fail:

.append(function(d) { 
  if(sometimesTrue) {
    return document.createElement('div');
  }
});

An else {} is required to return some other DOM element for the .append() to work properly.

This is something I saw with D3.v3 so not sure if handled better in newer versions. Hope that helps someone.

like image 43
Angelus Avatar answered Sep 21 '22 21:09

Angelus