d3 adding data attribute conditionally

跟風遠走 提交于 2019-12-31 10:22:34

问题


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 attributes and wondering if there is a way to do this.

This approach sort of works, by adding all the possible data attributes and leaving some blank, but I'm sure it's not good practise.

var th = d3.select(selection).select("thead").selectAll("th")
            .data(colspec)
            .enter().append("th")
            .text(function(d) { return d["data-name"]; })
            .attr("data-class", function(d) {
                if ("data-class" in d) {
                    return d["data-class"];
                } else {
                    return "";
                }
            })
            .attr("data-hide", function(d) {
                if ("data-hide" in d) {
                    return d["data-hide"];
                } else {
                    return "";
                }
            })
            .attr("data-ignore", function(d) {
                if ("data-ignore" in d) {
                    return d["data-ignore"];
                } else {
                    return "";
                }
            })

       etc.

colspec example:

[{"data-name": "username"}, {"data-name": "Date Joined", "data-hide": "true"}]

Currently getting:

  <th data-class="" data-hide="true" data-ignore="" data-type="">Joined</th>

Want

   <th  data-hide="true" >Joined</th>

Any suggestions?


回答1:


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]);
            });
        });



回答2:


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;
}, {}));



回答3:


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"];
        });



回答4:


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"])



回答5:


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

.call(condFunc,param)

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)

.each(d => condFunc)

Typical use:

 d3chain.each( d=> {
     if (param && d) d3.select(this).attr(d, g[param])
 })

See @nrabinowitz answer for detailed example.

.filter(d=>condFunc).etc

Typical use:

 d3chain.filter( d=> param in d ).attr(param, d=> g[param])

See @LarsKotthoff answer for detailed example.



来源:https://stackoverflow.com/questions/18205034/d3-adding-data-attribute-conditionally

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!