15

How can I get the value of the previous element of the dataset passed to .data() in d3?

I know in a callback I cant do something like

function(d,i) { console.log(d, i); })

for example, to print the data element and its index to the console. But how can i reference the previous element?

Like d[i-1] or something?

brno792
  • 6,479
  • 17
  • 54
  • 71
  • The below answer is quite involved. Did you try setting d equal to a variable, operating a callback on it, then updating the variable value with each d increment? – Union find Dec 31 '14 at 03:52

2 Answers2

23

You can get the value of previous element like this.

var texts = svg.selectAll("text")
    .data(data)
    .enter()
    .append("text")
    .attr("x",function(d){ return d.x; })
    .attr("y",function(d){ return d.y; })
    .text(function(d){ return d.name; });

texts.attr(...,function(d,i){
         ......
         var prevData = texts.data()[i-1]; //check whether i>0 before this code
         .......
     });

Here is a small example JSFiddle , Mouse over the texts to see the functionality.

Gilsha
  • 14,431
  • 3
  • 32
  • 47
8

There is no built in way to do it, but you can achieve it in all kinds of ways, including

Scope:

var data = ['a','b','c','d']
d3.select('.parent').selectAll('.child')
  .data(data)
.enter()
  .append('div')
  .attr('class', 'child')
  .text(function(d,i) {
    return "previous letter is " + data[i-1];
  });

Linking (works even if they're Strings, as in this example):

var data = ['a','b','c','d']
for(var i = 0; i < data.length; i++) { data[i].previous = data[i-1]; }
d3.select('.parent').selectAll('.child')
  .data(data)
...
.text(function(d,i) {
  return "previous letter is " + d.previous
});

Via the parent node (Experimental):

var data = ['a','b','c','d']
d3.select('.parent').selectAll('.child')
  .data(data)
...
.text(function(d,i) {
  var parentData = d3.select(this.parentNode).selectAll('.child').data();
  // NOTE: parentData === data is false, but parentData still deep equals data
  return "previous letter is " + parentData[i-1];
});

Related to the last example, you can even try to find the sibling DOM node immediately preceding this node. Something like

...
.text(function(d,i) {
  var previousChild = d3.select(this.parentNode).select('.child:nth-child(' + i + ')')
  return "previous letter is " + previousChild.datum();
})

but the last two can fail in all kinds of ways, like if the DOM nodes aren't ordered the same as data, or if there are other unrelated DOM nodes within the parent.

meetamit
  • 24,727
  • 9
  • 57
  • 68
  • 1
    isnt this answer better? why the other one has more upvotes? ಠ_ಠ –  Oct 27 '18 at 17:11
  • @Ammar, It depends I guess. The option in the other answer is more concise and likely easier to understand. If there’s not a lot of data involved it’s just fine. If data however is a long array, it can get quite unperformant, because d3 has to iterate over all elements in the selection and gather each one’s datum. And note that if data has n elements then the relatively expensive call to .data() will occur n times(!). – meetamit Nov 01 '18 at 01:01