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

javascript - How can I toggle the class of all elements in a selection? - Stack Overflow

programmeradmin2浏览0评论

I am trying to write a function that adds a class to all elements in a selection when those elements do not have that class yet, and vice versa:

function toggleLinksActivity(d) {
  d3.selectAll(".link")
    .filter(l => l.target == d)
    .classed("non-active", l => !l.classed("non-active"));
}

This gives an error that .classed is not a function. The documentation states that .classed should be called on a selection, so I tried changing the last line into !d3.select(l).classed("non-active"), but that does not work either ("t.getAttribute is not a function"). What is going wrong and what is the correct way?

Note: I have already solved my problem with the 2 separate functions underneath, but I find it ugly to split a toggle into 2 parts.

function activateLinks(d) {
  d3.selectAll(".link")
    .filter(l => l.target == d)
    .classed("non-active", false);
}

function deactivateLinks(d) {
  d3.selectAll(".link")
    .filter(l => l.target == d)
    .classed("non-active", true);
}

I am trying to write a function that adds a class to all elements in a selection when those elements do not have that class yet, and vice versa:

function toggleLinksActivity(d) {
  d3.selectAll(".link")
    .filter(l => l.target == d)
    .classed("non-active", l => !l.classed("non-active"));
}

This gives an error that .classed is not a function. The documentation states that .classed should be called on a selection, so I tried changing the last line into !d3.select(l).classed("non-active"), but that does not work either ("t.getAttribute is not a function"). What is going wrong and what is the correct way?

Note: I have already solved my problem with the 2 separate functions underneath, but I find it ugly to split a toggle into 2 parts.

function activateLinks(d) {
  d3.selectAll(".link")
    .filter(l => l.target == d)
    .classed("non-active", false);
}

function deactivateLinks(d) {
  d3.selectAll(".link")
    .filter(l => l.target == d)
    .classed("non-active", true);
}
Share Improve this question edited May 5, 2018 at 23:43 Gerardo Furtado 102k9 gold badges128 silver badges177 bronze badges asked May 5, 2018 at 15:50 JeroenJeroen 5443 silver badges28 bronze badges 3
  • It looks like your logic is ok -- but i suspect the filter parison l.target == d is not returning anything. Hard to tell without knowing what your input d contains, and what data(...) has been attached to your links... – SteveR Commented May 5, 2018 at 16:42
  • @SteveR I checked in the console that the filtering is done correctly. – Jeroen Commented May 5, 2018 at 16:48
  • 3 l is your datum, you need to select your element to be able to class the selection properly, this is why l.classed() and d3.select(l).classed do not work, you aren't using .classed() on a d3 selection in either approach. – Andrew Reid Commented May 5, 2018 at 16:59
Add a ment  | 

3 Answers 3

Reset to default 5

Gerardo's answer is pointing in the right direction by using Element.classList, but the proposed solution can still be simplified. As it turns out, the classList property already features a .toggle() method, which works as follows:

toggle( String [, force] )
When only one argument is present: Toggle class value; i.e., if class exists then remove it and return false, if not, then add it and return true. When a second argument is present: If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it.

Your function can thus be written as:

function toggleLinksActivity(d) {
  d3.selectAll(".link")
    .each(function(l) {
      if (l.target == d) {
        this.classList.toggle("non-active");
      }
    });
}

You need to reselect the ith element and use its classed property:

function toggleLinkActive() {
  d3.selectAll(".link")
    .classed("non-active", (d,i,nodes) => !d3.select(nodes[i]).classed("non-active"));
}

You can also select this if you are using a non-arrow function:

function toggleLinkActive() {
  d3.selectAll(".link")
    .classed("non-active", function() { return !d3.select(this).classed("non-active"); });
}

If you want to avoid reselecting the item, you can use each and store the selection in a variable:

function toggleLinkActive() {
  d3.selectAll(".link")
    .each(function() {
      const item = d3.select(this);
      item.classed("non-active", !item.classed("non-active"));
    });

Or you can use the native this.classList.toggle("non-active") like in @altocumulus' answer.

Instead of using the D3 classed method again inside itself we can use classList, toggling the class with a more short and elegant function that uses the unary negation and contains to check if the selection has a given class.

In my proposed function, you have to pass the selection and the name of the class. It's just this:

function toggleClass(selection, className) {
    selection.classed(className, !selection.node().classList.contains(className))
}

Explanation

The logic behind this function is quite simple: contains() returns a boolean... so, negating it (!true gives us false and !false gives us true) and passing that negated boolean to the classed method toggles the class in the selection.

Let's see it in action:

var paragraphs = d3.selectAll("p");

d3.interval(function() {
  toggleClass(paragraphs, "foo");
  console.log(paragraphs.attr("class"))
}, 1000)

function toggleClass(selection, className) {
  selection.classed(className, !selection.node().classList.contains(className))
}
<script src="https://d3js/d3.v5.min.js"></script>
<p class="foo someOtherClass">Foo</p>
<p class="foo someOtherClass">Bar</p>
<p class="foo someOtherClass">Baz</p>

发布评论

评论列表(0)

  1. 暂无评论