Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get "call" to execute for each data element in D3?

Using D3, I'm trying to implement a word wrap plugin for each quotation displayed from myData. The call function on the last line is that.

The problem is that it only works for the first quote. It makes the all other quotes not appear. I'm not sure how to structure the call to happen while enter() would typically renders thing.

var quote=svg.selectAll("text.quote").data(myData);
quote.exit().remove()
quote.enter().append("text")
quote
.attr("class","quote")
.attr("x", function (d,i){ return xScale(i);})
.attr("y", function(d){ return yScale(d.y);})
.text(function(d, i){return d.quote;})
.call(d3.util.wrap(125))
like image 523
koleslaw Avatar asked Nov 16 '17 18:11

koleslaw


People also ask

What do the select () and selectAll () functions in d3 do?

d3. select selects the first matching element whilst d3. selectAll selects all matching elements. Both functions take a string as its only argument.

How do you use d3 select this?

select() function in D3. js is used to select the first element that matches the specified selector string. If any element is not matched then it returns the empty selection. If multiple elements are matched with the selector then only the first matching element will be selected.

What is .attr in d3?

D3 provides the ability to set attributes of a selected element using the attr() function. This function takes two parameters: Attribute Name - For example, "r" to set an SVG circle's radius.


1 Answers

You want selection.each() rather than selection.call(). Selection.call will will invoke a function just once, while .each will invoke it for each datum:

selection.each(function) <>

Invokes the specified function for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). This method can be used to invoke arbitrary code for each selected element...

Compare to:

selection.call(function[, arguments…]) <>

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

(API Documentation (v4, but both methods exist in v3))

See the following snippet for a comparison of both:

var data = [10,20,30,40];

var selection = d3.select("body").selectAll(null)
  .data(data)
  .enter()
  .append("p")
  .each(function(d) {
    console.log("each: " + d); // d is datum
  })
  .call(function(d) {
    console.log("call: ") 
    console.log(d.data()); // d is selection
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

To call this function once on each item, you could use .call within the .each. I've used a g to place the text, so that the tspans this utility creates are positioned correctly (otherwise they overlap). The following snippet otherwise uses your code (the word wrap utility is on top as I could not find a cdn for it quickly enough):

d3.util = d3.util || {};

d3.util.wrap = function(_wrapW){ 
    return function(d, i){
        var that = this;

        function tspanify(){ 
            var lineH = this.node().getBBox().height;
            this.text('')
                .selectAll('tspan')
                .data(lineArray)
                .enter().append('tspan')
                .attr({
                    x: 0,
                    y: function(d, i){ return (i + 1) * lineH; } 
                })
                .text(function(d, i){ return d.join(' '); })
        }   

        function checkW(_text){ 
            var textTmp = that
                .style({visibility: 'hidden'})
                .text(_text);
            var textW = textTmp.node().getBBox().width;
            that.style({visibility: 'visible'}).text(text);
            return textW; 
        }

        var text = this.text();
        var parentNode = this.node().parentNode;
        var textSplitted = text.split(' ');
        var lineArray = [[]];
        var count = 0;
        textSplitted.forEach(function(d, i){ 
            if(checkW(lineArray[count].concat(d).join(' '), parentNode) >= _wrapW){
                count++;
                lineArray[count] = [];
            }
            lineArray[count].push(d)
        });

        this.call(tspanify)
    }
};

var wrap = d3.util.wrap(11);

var svg = d3.select("body")
 .append("svg")
 .attr("height",400)
 .attr("width",400);

var myData = ["text 1","text 2","text 3"]

var quote = svg.selectAll("text.quote").data(myData);
quote.enter().append("g")

quote.attr("class","quote")
.attr("transform", function (d,i){ return "translate(20," + (i * 40 + 20) + ")" })
.append("text")
.text(function(d, i){return d})
.each(function() {
  d3.select(this).call(d3.util.wrap(11));
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
like image 164
Andrew Reid Avatar answered Oct 05 '22 22:10

Andrew Reid