I\'m creating a table with d3 to be used by the FooTable jquery plugin and this requires having some data- attributes in the header row. But not all columns have all the data a
The most voted solution is perfect because .attr(a,b)
works as conditional when b
is null,
d3chain.attr('data-class', d=>'data-class' in d ? d['data-class'] : null );
but this solution is not geral, is not valid for other chaining methods, except using .each()
, .filter
or .call()
. In general the most simple is call()
.
Suppose that param
is an global variable used as parameter in the condition, and that g
is a global object used to return a value.
// inconditional
d3chain.attr(param, g[param])
// conditional case using globals
d3chain.call( s => { if (g[param]) s.attr(param,g[param]) })
// conditional case passing the parameter
d3chain.call( (s,p) => {
if (g[p]) s.attr(p, g[p])
}, param)
Typical use:
d3chain.each( d=> {
if (param && d) d3.select(this).attr(d, g[param])
})
See @nrabinowitz answer for detailed example.
Typical use:
d3chain.filter( d=> param in d ).attr(param, d=> g[param])
See @LarsKotthoff answer for detailed example.
You don't need to call each() or filter()... The attr() function will do this for you internally. Just call it with a function instead of a value, and have that function return the desired value for each datum, or null if the attribute is not desired for a particular datum, like so:
...
.attr('data-class', function(d) {
return 'data-class' in d ? d['data-class'] : null;
});
If your function returns null, the attribute is not added. You can even combine several attributes into one call by providing a map of attr names to functions like so:
...
.attr({
'data-class': function(d) {
return 'data-class' in d ? d['data-class'] : null;
},
'data-hide': function(d) {
return 'data-hide' in d ? d['data-hide'] : null;
},
'data-ignore': function(d) {
return 'data-ignore' in d ? d['data-ignore'] : null;
}
});
or if you're like me and would rather not type so much, you can reduce the list of attribute names into the appropriate map:
...
.attr(['data-class', 'data-hide', 'data-ignore'].reduce(function(result, attr) {
result[attr] = function(d) {
return attr in d ? d[attr] : null;
}
return result;
}, {}));
Seems like a good candidate for .each()
:
var th = d3.select(selection).select("thead").selectAll("th")
.data(colspec)
.enter().append("th")
.text(function(d) { return d["data-name"]; })
// now address each item individually
.each(function(d) {
var header = d3.select(this);
// loop through the keys - this assumes no extra data
d3.keys(d).forEach(function(key) {
if (key != "data-name")
header.attr(key, d[key]);
});
});
I often use .each
when having a per-item scope makes more sense than trying to figure out a bunch of attributes for each item.
For a short list of attributes, especially if you're worried about extra data in the objects, it's probably easier to loop through the desired keys instead of everything:
.each(function(d) {
var header = d3.select(this);
['data-class', 'data-hide', 'data-ignore'].forEach(function(key) {
if (key in d)
header.attr(key, d[key]);
});
});
You can use the .filter() function to only operate on the subset of the selection that you need to set attributes for, e.g.
var th = d3.select(selection).select("thead").selectAll("th")
.data(colspec)
.enter().append("th")
.text(function(d) { return d["data-name"]; });
th.filter(function(d) { return ("data-class" in d); })
.attr("data-class", function(d) {
return d["data-class"];
});
A cleaner is to use filter
.filter(d => !!d["data-class"]) // filter only data with the "data-class" property
.attr("data-class", d => d["data-class"])