manu08 manu08 - 7 days ago 4
Javascript Question

Drawing an HTML table via D3 doesn't update existing data

I'm using D3 to draw an HTML table, and things work great on enter. When I add a new item to my data collection, it adds the new item to the table correctly.

The problem is whenever I update an existing object (an object in the backgroundJobs collection below) within the collection. When I re-run the D3 code to sync up the table, it does not work. Nothing happens.

Here's the code:

var visibleColumns = ['Name', 'Start', 'End', 'Status', 'Metadata', 'Errors'];

var table = d3.select('#jobs').append('table');
var thead = table.append('thead');
var tbody = table.append('tbody');

thead.append("tr")
.selectAll("th")
.data(visibleColumns)
.enter()
.append("th")
.text(function (column) { return column; });

function tick() {
var rows = tbody.selectAll("tr")
.data(backgroundJobs, function(d) {
return d.name;
})
.enter()
.append("tr");

var cells = rows.selectAll("td")
.data(function(row) {
return [{column: 'Name', value: row.name},
{column: 'Start', value: row.startedTimestamp},
{column: 'End', value: row.endedTimestamp},
{column: 'Status', value: row.isRunning},
{column: 'Metadata', value: ''},
{column: 'Errors', value: row.errorMsg}];
})
.enter()
.append("td")
.text(function(d) { return d.value; });
}

setInterval(tick, 500);

Answer

Please refer to the cool explanation of the data joins.

When you call

tbody.selectAll("tr").data(some-new-data);

You actually get 3 selections: 'enter' (whith the new elements not present in the DOM yet), 'exit' (those present in DOM but not longer present in the data) and 'update' which contains nodes that are already in DOM and still have the data assigned to them via the .data call above.

In general, for 'enter' selection you create new nodes, for 'exit' you need to remove the old ones, and for 'update' you just change attributes - may be with some nice transition effect. See the updated 'tick' function code.

function tick() {
    var rows = tbody.selectAll("tr")
    .data(backgroundJobs, function(d) {
        return d.name;
    });

    rows.enter()
        .append("tr");

    rows.order();

    var cells = rows.selectAll("td")
        .data(function(row) {
            return [{column: 'Name', value: row.name},
                {column: 'Start', value: row.startedTimestamp},
                {column: 'End', value: row.endedTimestamp},
                {column: 'Status', value: row.isRunning},
                {column: 'Metadata', value: ''},
                {column: 'Errors', value: row.errorMsg}];
        });

    cells.enter()
        .append("td");

    cells.text(function(d) { return d.value;});

    cells.exit().remove();

    rows.exit().remove();
}

See the Demo (backgroundJobs is switched on timer between the two hard-coded datasets).

Comments