使用D3更新SVG元素Z-Index

时间:2012-11-27 23:34:39

标签: javascript svg d3.js

使用D3库将SVG元素置于z顺序顶部的有效方法是什么?

我的具体情况是一个饼图,当鼠标悬停在给定的片段上时,它会突出显示(通过向stroke添加path)。生成我的图表的代码块如下:

svg.selectAll("path")
    .data(d)
  .enter().append("path")
    .attr("d", arc)
    .attr("class", "arc")
    .attr("fill", function(d) { return color(d.name); })
    .attr("stroke", "#fff")
    .attr("stroke-width", 0)
    .on("mouseover", function(d) {
        d3.select(this)
            .attr("stroke-width", 2)
            .classed("top", true);
            //.style("z-index", 1);
    })
    .on("mouseout", function(d) {
        d3.select(this)
            .attr("stroke-width", 0)
            .classed("top", false);
            //.style("z-index", -1);
    });

我尝试过几个选项,但到目前为止还没有运气。使用style("z-index")并调用classed两者都不起作用。

“top”类在我的CSS中定义如下:

.top {
    fill: red;
    z-index: 100;
}

fill声明是为了确保我知道它正确打开/关闭。它是。

我听说使用sort是一个选项,但我不清楚如何将“selected”元素置于顶部。

更新

我使用以下代码修复了我的特殊情况,该代码在mouseover事件上向SVG添加了一个新弧以显示突出显示。

svg.selectAll("path")
    .data(d)
  .enter().append("path")
    .attr("d", arc)
    .attr("class", "arc")
    .style("fill", function(d) { return color(d.name); })
    .style("stroke", "#fff")
    .style("stroke-width", 0)
    .on("mouseover", function(d) {
        svg.append("path")
          .attr("d", d3.select(this).attr("d"))
          .attr("id", "arcSelection")
          .style("fill", "none")
          .style("stroke", "#fff")
          .style("stroke-width", 2);
    })
    .on("mouseout", function(d) {
        d3.select("#arcSelection").remove();
    });

10 个答案:

答案 0 :(得分:84)

正如其他答案所解释的那样,SVG没有z-index的概念。相反,文档中元素的顺序决定了图形中的顺序。

除了手动重新排序元素外,某些情况还有另一种方法:

使用D3时,您经常会有某些类型的元素,这些元素应始终在其他类型的元素之上绘制

例如,在布局图表时,链接应始终位于节点下方。更一般地说,一些背景元素通常需要放在其他所有元素之下,而一些高光和叠加层应该放在上面。

如果您遇到这种情况,我发现为这些元素组创建父组元素是最好的方法。在SVG中,您可以使用g元素。例如,如果您的链接应始终位于节点下方,请执行以下操作:

svg.append("g").attr("id", "links")
svg.append("g").attr("id", "nodes")

现在,当您绘制链接和节点时,请选择如下(以#开头的选择器引用元素ID):

svg.select("#links").selectAll(".link")
// add data, attach elements and so on

svg.select("#nodes").selectAll(".node")
// add data, attach elements and so on

现在,所有链接将始终在所有节点元素之前进行结构性附加。因此,SVG将显示所有节点下的所有链接,无论您添加或删除元素的频率和顺序如何。当然,所有相同类型的元素(即在同一个容器中)仍将遵循它们的添加顺序。

答案 1 :(得分:47)

开发人员提出的解决方案之一是:“使用D3的排序运算符对元素进行重新排序。” (见https://github.com/mbostock/d3/issues/252

从这个角度来看,人们可以通过比较他们的数据或者如果他们是无数据元素的位置来对元素进行排序:

.on("mouseover", function(d) {
    svg.selectAll("path").sort(function (a, b) { // select the parent and sort the path's
      if (a.id != d.id) return -1;               // a is not the hovered element, send "a" to the back
      else return 1;                             // a is the hovered element, bring "a" to the front
  });
})

答案 2 :(得分:20)

由于SVG没有Z-index但是使用DOM元素的顺序,你可以通过以下方式将它带到前面:

this.parentNode.appendChild(this);

然后你可以利用insertBefore将其重新放回mouseout。但是,这要求您能够在之前插入元素的兄弟节点。

DEMO:请看一下这个JSFiddle

答案 3 :(得分:13)

SVG不做z-index。 Z顺序由其容器中SVG DOM元素的顺序决定。

就我所知(过去我曾经尝试过几次),D3没有提供分离和重新连接单个元素的方法,以便将它带到前面或其他地方。

有一个.order() method,它重新调整节点以匹配它们在选择中出现的顺序。在您的情况下,您需要将单个元素放在前面。因此,从技术上讲,您可以选择前面所需的元素(或最后,不记得哪个是最顶层),然后在其上调用order()

或者,您可以跳过d3执行此任务,并使用普通JS(或jQuery)重新插入该单个DOM元素。

答案 4 :(得分:6)

简单的答案是使用d3排序方法。除了d3.select(' g')。order()之外,版本4中还有.lower()和.raise()。这会更改元素的显示方式。有关详细信息,请参阅文档 - https://github.com/d3/d3/blob/master/API.md#selections-d3-selection

答案 5 :(得分:4)

我在我的代码中实现了futurend的解决方案并且它有效,但是由于我使用了大量元素,所以它非常慢。这是使用jQuery的替代方法,它对我的​​特定可视化更快。它依赖于你想要的svgs最常见的一个类(在我的例子中,该类在我的数据集中被标记为d.key)。在我的代码中有一个<g>,其中包含“location”类,其中包含我正在重新组织的所有SVG。

.on("mouseover", function(d) {
    var pts = $("." + d.key).detach();
    $(".locations").append(pts);
 });

因此,当您将鼠标悬停在特定数据点上时,代码会查找具有该特定类的SVG DOM元素的所有其他数据点。然后它分离并重新插入与这些数据点关联的SVG DOM元素。

答案 6 :(得分:4)

想要扩展@ notan3xit的答案,而不是写出一个全新的答案(但我没有足够的声誉)。

解决元素顺序问题的另一种方法是使用&#39; insert&#39;而不是&#39;追加&#39;画画的时候。这样,路径将始终在其他svg元素之前放在一起(假设您的代码已经为其他svg元素的enter()之前的链接执行了enter())。

d3插入api:https://github.com/mbostock/d3/wiki/Selections#insert

答案 7 :(得分:1)

我花了很多时间才找到如何在现有SVG中调整Z顺序。我真的需要在具有工具提示行为的d3.brush环境中使用它。为了使这两个特征很好地协同工作(http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html),你需要d3.brush成为Z顺序中的第一个(第一个在画布上绘制,然后由其余的SVG元素覆盖)并且它将捕获所有鼠标事件,无论它是什么(具有更高的Z指数)。

大多数论坛评论都说你应该先在你的代码中添加d3.brush,然后再添加你的SVG&#34;绘图&#34;码。但对我来说,这是不可能的,因为我加载了一个外部SVG文件。您可以随时轻松添加画笔,稍后使用以下方法更改Z顺序:

d3.select("svg").insert("g", ":first-child");

在d3.brush设置的上下文中,它将如下所示:

brush = d3.svg.brush()
    .x(d3.scale.identity().domain([1, width-1]))
    .y(d3.scale.identity().domain([1, height-1]))
    .clamp([true,true])
    .on("brush", function() {
      var extent = d3.event.target.extent();
      ...
    });
d3.select("svg").insert("g", ":first-child");
  .attr("class", "brush")
  .call(brush);

d3.js insert()函数API:https://github.com/mbostock/d3/wiki/Selections#insert

希望这有帮助!

答案 8 :(得分:0)

版本1

理论上,以下情况应该可以正常进行。

CSS代码:

path:hover {
    stroke: #fff;
    stroke-width : 2;
}

此CSS代码将向所选路径添加笔划。

JS代码:

svg.selectAll("path").on("mouseover", function(d) {
    this.parentNode.appendChild(this);
});

此JS代码首先从DOM树中删除路径,然后将其添加为其父级的最后一个子级。这样可以确保在同一父节点的所有其他子节点之上绘制路径。

实际上,此代码在Chrome中运行良好,但在其他一些浏览器中有所突破。我在我的Linux Mint机器上的Firefox 20中尝试过,无法让它工作。不知何故,Firefox无法触发:hover样式,我还没有办法解决这个问题。


第2版

所以我提出了另一种选择。它可能有点'脏',但至少它可以工作,它不需要循环所有元素(作为其他一些答案)。

CSS代码:

path.hover {
    stroke: #fff;
    stroke-width : 2;
}

我没有使用:hover伪选择器,而是使用.hover

JS代码:

svg.selectAll(".path")
   .on("mouseover", function(d) {
       d3.select(this).classed('hover', true);
       this.parentNode.appendChild(this);
   })
   .on("mouseout", function(d) {
       d3.select(this).classed('hover', false);
   })

在鼠标悬停时,我将.hover类添加到我的路径中。在mouseout上,我删除它。 与第一种情况一样,代码也会从DOM树中删除路径,然后将其添加为其父项的最后一个子项。

答案 9 :(得分:0)

你可以这样做鼠标悬停你可以把它拉到顶部。

d3.selection.prototype.bringElementAsTopLayer = function() {
       return this.each(function(){
       this.parentNode.appendChild(this);
   });
};

d3.selection.prototype.pushElementAsBackLayer = function() { 
return this.each(function() { 
    var firstChild = this.parentNode.firstChild; 
    if (firstChild) { 
        this.parentNode.insertBefore(this, firstChild); 
    } 
}); 

};

nodes.on("mouseover",function(){
  d3.select(this).bringElementAsTopLayer();
});

如果你想要推回去

nodes.on("mouseout",function(){
   d3.select(this).pushElementAsBackLayer();
});