Ondra Žižka Ondra Žižka - 1 year ago 166
CSS Question

D3.js: Adding class whose name is based on data

D3 has 2 ways of setting a class:

selection.attr("class", (d) => d.isFoo ? "foo" : "");
selection.classed("foo", (d) => d.isFoo)


I'm looking for a way to add a class named as per the data. I.e. something like:

selection.classed((d) => "node" + d.id, true);



  • I don't want to use
    id
    because multiple DOM elements will share that class.

  • I don't want to use
    attr
    because it may potentially overwrite other classes already there.



So I could do

selection.attr("class", (d) => d3.select(this).attr("class") + " node" + d.id);


which feels a bit hackish.

Any better solution? Or a feature underway?

Answer Source

Unfortunately, you cannot pass a function to classed() the way you want (i.e., as the first argument). The documentation is clear about classed():

selection.classed(names[, value])

If a value is specified, assigns or unassigns the specified CSS class names on the selected elements by setting the class attribute or modifying the classList property and returns this selection. The specified names is a string of space-separated class names. (emphasis mine)

Thus, you cannot pass a function to the class name. The class name there has to be fixed.

That being said, what you're doing right now to add a class name without overwriting a previously existing class...

selection.attr("class", (d) => d3.select(this).attr("class") + " node" + d.id);

... seems to be the standard way among D3 coders, even if you feel that it's a hack, but with a small modification: don't use this with an arrow function, it's not going to work (check this example at S.O. Docs: Using "this" with an arrow function). Thus, this is the correct snippet:

selection.attr("class", function (d){
    d3.select(this).attr("class") + " node" + d.id);
});

But, just to show you how to do it using classed(), I created a demo code that is way more hackish than your solution, just for the sake of curiosity. Have a look at it:

var data = [{name: "foo"}, 
            {name: "bar"},
            {name: "baz"}];

var body = d3.select("body");

var divs = body.selectAll("myDivs")
    .data(data)
    .enter()
    .append("div")
    .attr("class", "someClass");

//here comes the hack:
divs.each(function(d) {
    d3.select(this).classed("node" + d.name, () => d3.select(this).datum().name == d.name)
});

//log the classes, you can see the previous class ("someClass") was not overwritten:
divs.each(function() {
    console.log(d3.select(this).attr("class"))
})
<script src="https://d3js.org/d3.v4.min.js"></script>

As d.name is provided by the each() function, the first argument ("node" + d.name) is in fact a string.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download