Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
like image 869
reustmd Avatar asked Aug 02 '13 17:08

reustmd


2 Answers

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).

like image 67
amakhrov Avatar answered Sep 19 '22 10:09

amakhrov


The rows variable will be a selection that will only contains nodes if enter() is not empty. The second time around, if you haven't added any new rows into backgroundJobs, the data bind will update the existing nodes and enter() won't contain any nodes (meaning rows won't contain any nodes).

You can get around this by holding a reference to the update selection taking advantage of the fact that nodes appended to the enter selection get added to the update selection behind the scenes:

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

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

Now rows will refer to a selection that contains all previously existing and newly added nodes.

like image 33
Scott Cameron Avatar answered Sep 21 '22 10:09

Scott Cameron