最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - D3.js: Adding class whose name is based on data - Stack Overflow

programmeradmin4浏览0评论

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?

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?

Share Improve this question edited Feb 3, 2017 at 1:43 Gerardo Furtado 102k9 gold badges128 silver badges176 bronze badges asked Jan 31, 2017 at 17:37 Ondra ŽižkaOndra Žižka 46.8k48 gold badges226 silver badges298 bronze badges 2
  • selection.attr("class", (d) => d3.select(this).attr("class") + " node" + d.id); is exactly the same as selection.classed((d) => "node" + d.id, true); – Trash Can Commented Jan 31, 2017 at 19:20
  • 1 @Dummy No, it's not the same. If you do this selection.classed((d) => "node" + d.id, true);, it will give all your elements this strange class: ".(d) => "node" + d.name". Strange and not valid, by the way. The whole function will be treated as a string. Try it and you'll see. – Gerardo Furtado Commented Feb 1, 2017 at 2:42
Add a comment  | 

3 Answers 3

Reset to default 10

Although Gerardo's answer is correct and comes with a proper explanation, I think, the easiest way to mess with an element's classes is to resort to its classList property which it inherits from the Element interface.

The Element.classList is a read-only property which returns a live DOMTokenList collection of the class attributes of the element.

Although the property itself is read-only, it provides methods for adding, removing and toggling classes. Calling .add() will automatically take care of classes previously set and check if the class to add has already been assigned to the element:

add( String [, String] )

Add specified class values. If these classes already exist in attribute of the element, then they are ignored.

Adding a class based on data can thus be done using selection.each():

divs.each(function(d) {
  this.classList.add("node" + d.name);
});

Within the .each() method this points to the actual element and can be used to directly access the classList property.

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

divs.each(function(d) {
  this.classList.add("node" + 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>

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. However, you need a small modification here: don't use this with an arrow function, it's not going to work (check this example d3 v4 retrieve drag DOM target from drag callback when `this` is not available). Thus, this is the correct snippet:

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

Yet, it is possible to use classed() the way you want, not directly (again, you cannot pass a function to it), but in a odd workaround. Just to show you a way to do it using classed(), I created a demo code that is way more hackish than your solution, 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.

I faced the same problem and found a kind of good solution that is less hacky and may be of interest for anyone that end's up here. So, instead of trying to do something like:

.cls-type-1 { ... }
.cls-type-2 { ... }
selection.classed(d => d.className, true)

I ended up switching the classes by atribute selectors, so the js part is straightforward.

*[someAttr="cls-type-1"] { ... }
*[someAttr="cls-type-2"] { ... }
selection.attr("someAttr", d => d.className)

The only drawbacks I can think of is that you need to hardcode an attribute name and that removing the attribute isn't as easy with d3 as removing a class.

发布评论

评论列表(0)

  1. 暂无评论