2

Say if there is a table like this:

var data = [
    ['Orange', 'Orange', [6,3,3,2,5]],
    ['Apple', 'Red', [6,2,6,5,5]],
    ['Grape', 'Purple', [9,1,2,3,1]]
]

I'd like the strings to represented as strings, but the number array represented as a D3 line chart. If it's just text that I care about, I can selectAll td elements and insert some text.

var tcells = trows
    .selectAll("td")
    .data(function(d, i) { return d; })
    .enter()
    .append("td")
    .text(function(d, i) { return d; });

The 3rd column is text so I'd like to update it with graphs or alternatively append another column of graphs. Say lines is a function that creates a line graph, I would like to call lines() as it passes data from the 3rd column for each row.

trows.selectAll("td")
    .data(function(d) {return d[2]}) // scope out data from 3rd column
    .enter()
    .append("td")
    //.call(lines([3,8,2,5,8,4])); // this works
    .call(lines);                // this doesn't

My D3 knowledge is spotty so I am not clear on how the data and the selection are passed.

Here is the full code: jsfiddle link

tokestermw
  • 336
  • 3
  • 13

1 Answers1

2

You're confusing the selection.call(func) method with the selection.each(func) method.

call is a convenience method for calling functions that accept the selection as a whole as a parameter. trows.call(lines) is equivalent to lines(trows), except that it can be part of a method chain.

each is used to call a function once for each element in the selection, passing the data and index as parameters.

As for why trows.call(lines([3,8,2,5,8,4])) "works", well it only sort-of works. You're calling the function lines, with the hard-coded data array, but that function doesn't return anything, so there is nothing to call on the selection itself. (If you don't understand the difference, you might find this answer useful.)

Your method also currently only appends the line graph to the body, it doesn't insert it into the table. You have the following code that you acknowledge doesn't work:

// dynamic selection doesn't
this.append("td").append('svg')
    .attr('width', width).attr('height', height)
    .append('path').attr('class','line')
    .datum(data).attr('d', line);

There are a few reasons that is failing:

  • in the context you're calling the function, with lines([3,8,2,5,8,4]) being called directly, the this value will not be set, so will point to the window object;
  • even if you were calling the function with an each method (which sets the this value), this would point to the DOM node, not a selection that you can call d3 methods for;
  • except in your specific case, it wouldn't even point to a DOM node, because you're calling it from an enter() selection in which you haven't appended the new nodes yet.

In summary, to get things working:

  1. In the main program, use each to call your lines function, after creating your new <td> elements:

    trows.selectAll("td.graph")  
         //use a class so you don't re-select the existing <td> elements
      .data(function(d) {return [d[2]];})
         // the value returned by a data function must always be an array
         // containing data objects for each new element you create -- 
         // you only want one element containing the entire data array, 
         // not individual elements for each number, so wrap it in another array
      .enter()
        .append("td")
        .attr("class", "graph")
        .each(lines); 
    
  2. In the lines function, re-select the this element (which will now point to the just-created <td>), and append your sparkline SVG to it:

    d3.select(this).append('svg')
            .attr('width', width)
            .attr('height', height)
         .append('path')
            .attr('class','line')
            .datum(data)
            .attr('d', line);
    

http://jsfiddle.net/Lgq6ct9f/9/
(with some other clean-up so you don't have a function definition in the middle of your main program)

Community
  • 1
  • 1
AmeliaBR
  • 27,344
  • 6
  • 86
  • 119