【问题标题】:D3.js: Adding class whose name is based on dataD3.js:添加名称基于数据的类
【发布时间】:2017-06-17 06:26:27
【问题描述】:

D3 有两种设置类的方法:

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

我正在寻找一种方法来添加一个根据数据命名的类。 IE。类似:

selection.classed((d) => "node" + d.id, true);
  • 我不想使用id,因为多个 DOM 元素将共享该类。
  • 我不想使用attr,因为它可能会覆盖已经存在的其他类。

所以我可以这样做

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

感觉有点hackish。

有更好的解决方案吗?还是正在开发的功能?

【问题讨论】:

  • selection.attr("class", (d) => d3.select(this).attr("class") + " node" + d.id);selection.classed((d) => "node" + d.id, true); 完全相同
  • @Dummy 不,不一样。如果你这样做selection.classed((d) => "node" + d.id, true);,它会给你的元素all这个奇怪的类:".(d) => "node" + d.name"。顺便说一句,奇怪且无效。整个函数将被视为一个字符串。试试看,你会看到的。

标签: javascript css d3.js


【解决方案1】:

不幸的是,您不能以您想要的方式(即作为第一个参数)将函数传递给classed()documentation 清楚 classed():

选择.classed(names[, value])

如果指定了值,则通过设置 class 属性或修改 classList 属性为选定元素分配或取消分配指定的 CSS 类名称并返回此选择。指定的名称是由空格分隔的类名组成的字符串。 (强调我的)

因此,您不能将函数传递给类名。那里的类​​名必须是固定的

话虽如此,你现在正在做的是添加类名而不覆盖以前存在的类...

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

... 似乎是 D3 编码人员的标准方式,即使您觉得这是一种 hack。但是,您需要在此处进行小修改:不要将this 与箭头函数一起使用,它不会起作用(查看此示例d3 v4 retrieve drag DOM target from drag callback when `this` is not available)。因此,这是正确的 sn-p:

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

然而,可以按照你想要的方式使用classed(),而不是直接使用(同样,你不能将函数传递给它),而是以一种奇怪的解决方法。只是为了向您展示一种使用 classed() 的方法,出于好奇,我创建了一个演示代码,它比您的解决方案更骇人听闻。看看吧:

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>

由于d.name 是由each() 函数提供的,所以第一个参数("node" + d.name)实际上是一个字符串。

【讨论】:

    【解决方案2】:

    虽然 Gerardo 的 answer 是正确的并且带有适当的解释,但我认为,弄乱元素类的最简单方法是诉诸其从 Element 接口继承的 classList 属性。

    Element.classList 是一个只读属性,它返回元素类属性的实时DOMTokenList 集合。

    虽然属性本身是只读的,但它提供methods 用于添加、删除和切换类。调用.add() 将自动处理之前设置的类,并检查要添加的类是否已分配给元素:

    添加(字符串[,字符串])

    添加指定的类值。如果这些类已经存在于元素的属性中,那么它们将被忽略。

    因此可以使用selection.each() 来添加基于数据的类:

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

    .each() 方法内this 指向实际元素,可用于直接访问classList 属性。

    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>

    【讨论】:

    • 很棒的发现,谢谢!虽然,它也是一个非常新的功能:caniuse.com/#feat=classlist
    • 嗯,这不是新... 在撰写本文时,大多数浏览器都支持该属性。它已经在 FF 和 Chrome 中使用了 4 年!另一方面,IE 10 和 11 仍然“不支持 SVG 或 MathML 元素上的 classList。”。由于您正在处理 div 并且几乎所有其他浏览器似乎都可以使用它,所以我会尝试一下。
    • 好的,试过了,工作正常,可能支持到足以依赖它。我正在更改已接受的答案。
    猜你喜欢
    • 2023-04-02
    • 2012-03-29
    • 1970-01-01
    • 2017-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多