Question

I have this reusable pattern to create a table, inspired by http://bl.ocks.org/3687826, and I have two questions about it.

This is the function:

d3.table = function(config) {
var columns = [];

var tbl = function(selection) {
if (columns.length == 0) columns = d3.keys(selection.data()[0][0]);
console.log(columns)    

// Creating the table
var table = selection.append("table");
var thead = table.append("thead");
var tbody = table.append("tbody");

// appending the header row
var th = thead.selectAll("th")
        .data(columns)

th.enter().append("th");
    th.text(function(d) { return d });
th.exit().remove()

// creating a row for each object in the data
var rows = tbody.selectAll('tr')
    .data(function(d) { return d; })

rows.enter().append("tr");
rows.attr('data-row',function(d,i){return i});
rows.exit().remove();   

// creating a cell for each column in the rows
var cells = rows.selectAll("td")
        .data(function(row) {
    return columns.map(function(key) {
                return {key:key, value:row[key]};
    });
        })

cells.enter().append("td");
cells.text(function(d) { return d.value; })
    .attr('data-col',function(d,i){return i})
    .attr('data-key',function(d,i){return d.key});
cells.exit().remove();

return tbl;
};

tbl.columns = function(_) {
if (!arguments.length) return columns;
columns = _;
return this;
};

return tbl;
};

This table can be called as follows:

/// new table
var t = d3.table();

/// loading data
d3.csv('reusable.csv', function(error,data) {
    d3.select("body")
    .datum(data.filter(function(d){return d.price<850})) /// filter on lines
    .call(t)
});

where the reusable.csv file is something like this:

date,price
Jan 2000,1394.46
Feb 2000,1366.42
Mar 2000,1498.58
Apr 2000,1452.43
May 2000,1420.6
Jun 2000,1454.6
Jul 2000,1430.83
Aug 2000,1517.68
Sep 2000,1436.51

and the number of columns can be updated by

t.columns(["price"]); 
d3.select("body").call(t);

The problem is that another table with thead and tbody is created while updating, because the creation of the table is inside the function.

How can I say "create the table only one time, then update"?

Another question is: how can I filter the lines using a method inside the function?

Was it helpful?

Solution

The problem is with these three lines of code:

// Creating the table
var table = selection.append("table");
var thead = table.append("thead");
var tbody = table.append("tbody");

which always appends new table, thead, and tbody elements to your document. Here's how you can do this conditionally, only when these elements don't already exist (the example you cite creates its div.header element similarly):

selection.selectAll('table').data([0]).enter().append('table');
var table = selection.select('table');

table.selectAll('thead').data([0]).enter().append('thead');
var thead = table.select('thead');

table.selectAll('tbody').data([0]).enter().append('tbody');
var tbody = table.select('tbody');

The selectAll().data([0]).enter().append() pattern conditionally creates a single element if it isn't found. The cited example used data([true]) but any array with a single element will do.

To filter the nested data from within your function, change your call to data() and pass a filtered subset of the selection data like this:

var rows = tbody.selectAll('tr').data(tbody.data()[0].filter(function(d) { 
    return d.price > 1400; 
}));

Good luck!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top