最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - D3 console.log in a function chain - Stack Overflow

programmeradmin7浏览0评论

Is there a base function/object in D3 which would allow "in-line" logging?

I'm reading Scott Murry's nifty D3 book and want to do this:

  d3.select("body").selectAll("div")
    .log("body")
    .data(dataset)
    .log("data")
    .enter()
    .log("enter")
    .append("div")
    .log("div")
    .attr("class", "bar")
    .log("class");

I currently do this Horrible Thing:

const log = function (msg, val) {
  const data = val ? val : this
  const label = msg ? msg : "data:"
  console.log(label, data)
  return data
}

Object.prototype.log = log

This works fine, producing this clickable console output: But .. What is the D3 way to do this?

Is there a base function/object in D3 which would allow "in-line" logging?

I'm reading Scott Murry's nifty D3 book and want to do this:

  d3.select("body").selectAll("div")
    .log("body")
    .data(dataset)
    .log("data")
    .enter()
    .log("enter")
    .append("div")
    .log("div")
    .attr("class", "bar")
    .log("class");

I currently do this Horrible Thing:

const log = function (msg, val) {
  const data = val ? val : this
  const label = msg ? msg : "data:"
  console.log(label, data)
  return data
}

Object.prototype.log = log

This works fine, producing this clickable console output: But .. What is the D3 way to do this?

Share Improve this question edited Apr 13, 2018 at 0:02 Gerardo Furtado 102k9 gold badges128 silver badges176 bronze badges asked Apr 12, 2018 at 16:56 backspacesbackspaces 3,9427 gold badges38 silver badges62 bronze badges 2
  • 1 Thanks! Great answers. D3 is not only wonderful for data, but many other things as well. I need to seriously get my head around the D3 FPish style and adapt it to my current project. Wow. – backspaces Commented Apr 12, 2018 at 21:44
  • 2 After hundreds of questions along the lines of "hey, I wanna has bar chartz, gimme bar chartz pls", it's nice seeing a question like this! – Gerardo Furtado Commented Apr 13, 2018 at 0:00
Add a ment  | 

3 Answers 3

Reset to default 7

For logging datas in your chain, you can just use a fake attribute :

d3.select("body").selectAll("div")
  .data(dataset)
  .enter()
  .append("div")
  .attr("fake", d=> console.log(d));

For the other things, I don't think there is a "d3" way which allows to log and sadly, I think you have to do it with a normal way :

console.log(d3.select("body").selectAll("div").data(dataset).enter());

You could use your solution by modifying the d3 selection prototype (as opposed to the object prototype, I don't think that could be considered "horrible" - though I'm a little partial to messing with d3 internally), but I feel you are looking for selection.call().

If you want to access the current selection, but not interrupt method chaining, selection.call is probably your best bet:

Invokes the specified function exactly once, passing in this selection along with any optional arguments. Returns this selection. (docs)

The called function's first parameter will be the selection, further optional parameters can be passed in the call method itself. The general form is:

selection.call(func, arg, arg, ...)

function func(selection, arg, arg, ...) {}

Using selection.call for logging shouldn't be too hard to rig up with this method. Here's a quick mock up (v4/5):

var dataset = d3.range(10);

  d3.select("body").selectAll("div")
    .call(log,"body")
    .data(dataset)
    .call(log,"dataset")
    .enter()
    .call(log,"enter")
    .append("div")
    .call(log,"div")
    .attr("class", "bar")
    .call(log,"bar");

function log(sel,msg) {
  console.log(msg,sel);
}

And the results:

Here's the mock up in action.

The first thing that came to my mind was the approach proposed by Andrew Reid in his answer making use of selection.call(). Although that solution is fine, I think it can be improved by directly extending D3's selection. The docs on d3.selection() explicity state that this function can be used to extend the selection prototype.

You can create your own logger somewhat along the following lines:

const logger = {
  log() {
    console.log(...arguments);
    return this;
  }
}

This can be easily mixed in the d3.selection.prototype:

Object.assign(d3.selection.prototype, logger);

The simple method logger.log() just logs all arguments passed to the call to the console and facilitates method chaining by returning this, which refers to the selection itself.

Of course, one can easily imagine many more useful logging methods for printing attributes, selections, data and much more. The following demo expands on the basic concept:

const logger = {
  log() {          // Generic method logging all arguments.
    console.log(...arguments);
    return this;
  },
  
  logMsg(msg) {    // Logging just a simple msg.
    console.log(msg);
    return this;
  },
  
  logSel() {       // Log the selection.
    console.log(this);
    return this;
  },
  
  logAttr(name) {  // Log the attributes with "name" for all selected elements.
    this.each(function(d, i) {
      let attr = d3.select(this).attr(name);
      console.log(`Node ${i}: ${name}=${attr}`);
    });
    return this;
  },
  
  logData() {      // Log the data bound to this selection.
    console.log(this.data());
    return this;
  },

  logNodeData() {  // Log datum per node.
    this.each(function(d, i) {
      console.log(`Node ${i}: ${d}`);
    });
    return this;
  }
};

Object.assign(d3.selection.prototype, logger);

d3.select("body")
    .logMsg("Start")
  .append("svg").selectAll(null)
    .log(1, {}, "Test")
    // .logSel()   // this doesn't work well in Stack snippets
  .data([1,2])
  .enter().append("circle")
    .attr("r", "10")
    .logAttr("r")
    .logData()
    .logNodeData()
    .logMsg("End");
<script src="https://d3js/d3.v5.js"></script>

发布评论

评论列表(0)

  1. 暂无评论