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);
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With