使用D3更新SVG元素Z-索引


81

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

我的特定情况是一个饼图其中突出(通过添加strokepath)当鼠标在给定的片。生成我的图表的代码块如下:

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均无效。

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

.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();
    });

Answers:


52

开发人员提出的解决方案之一是:“使用D3的sort运算符对元素进行重新排序”。(请参阅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
  });
})

这不仅适用于Z顺序,也不会破坏刚开始于引发元素的任何拖动。
奥利弗·博克

3
它对我也不起作用。a,b似乎只是数据,而不是d3选择元素或DOM元素
nkint 2013年

确实,就像@nkint一样,在与a.id和比较时,它对be无效d.id。解决方案是直接比较ad
和平使人充裕2014年

这对于大量的子元素来说效果不佳。最好在后续中重新渲染凸起的元素<g>
tribalvibes 2014年

1
或者,svg:use元素可以动态指向凸起的元素。有关解决方案,请参见stackoverflow.com/a/6289809/292085
tribalvibes 2014年

91

如其他答案所述,SVG没有z索引的概念。相反,文档中元素的顺序决定了图形中的顺序。

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

使用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
当我遇到真正的问题是将元素组织成组时,我想到了可以上移一个项目的想法。如果您的“实际”问题是移动单个元素,那么上一个答案中描述的排序方法是一个不错的选择。
约翰·戴维

太棒了!谢谢,notan3xit,您保存了我的一天!只是为他人添加一个想法-如果您需要以与所需的可见性顺序不同的顺序添加元素,则可以按照适当的可见性顺序创建组(甚至在向其添加任何元素之前),然后将元素添加到这些元素中以任何顺序分组。
Olga Gnatenko 2014年

1
这是正确的方法。一旦想到它就很明显。
戴夫

对我来说,svg.select('#links')...必须替换为d3.select('#links')...才能正常工作。也许这会帮助别人。
交换

26

由于SVG没有Z-index,但是使用DOM元素的顺序,因此可以通过以下方式将其放在前面:

this.parentNode.appendChild(this);

然后,您可以例如使用insertBefore将其放回mouseout。但是,这要求您能够定位要在其之前插入元素的兄弟节点。

演示:看看这个JSFiddle


如果不是因为Firefox无法呈现任何:hover样式,这将是一个很好的解决方案。我也认为此用例不需要最后的“ nsert”功能。
John Slegers 2014年

@swenedo,您的解决方案对我来说非常有用,并将元素置于最前面。关于将其放回去,我有点堆积,不知道如何以正确的方式编写它。能否请您举个例子,说明如何将物体放在后面。谢谢。
Mike B.

1
@MikeB。当然,我刚刚更新了答案。我意识到d3的insert功能可能不是最好的方法,因为它创建了一个元素。我们只想移动一个现有元素,因此insertBefore在本示例中将使用它。
swenedo

我尝试使用此方法,但是很遗憾,我无法在IE11中工作。您对此浏览器有解决方法吗?
Antonio Giovanni Schiavone

13

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

据我所知(过去我已经尝试过几次),D3没有提供分离和重新附加单个元素的方法来将其放置在最前或其他位置。

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

或者,您可以跳过d3来完成此任务,并使用纯JS(或jQuery)重新插入单个DOM元素。


5
请注意,在某些浏览器中,将鼠标悬停处理程序内的元素删除/插入时会出现问题,这可能导致无法正确发送mouseout事件,因此,建议您在所有浏览器中尝试执行此操作,以确保它能按预期工作。
ErikDahlström'12

更改顺序是否还会更改饼图的布局?我也在研究jQuery,以学习如何重新插入元素。
Evil Closet Monkey

我已经用我选择的解决方案更新了问题,但实际上并没有利用原始问题的核心。如果您对此解决方案有任何不满意之处,欢迎发表评论!感谢您对原始问题的见解。
Evil Closet Monkey

似乎是一种合理的方法,主要是由于重叠形状没有填充的事实。如果确实如此,我希望它在出现时立即“窃取”鼠标事件。而且,我认为@ErikDahlström的观点仍然成立:在某些浏览器(主要是IE9)中,这仍然可能是一个问题。为了获得额外的“安全性”,您可以添加.style('pointer-events', 'none')覆盖路径。不幸的是,此属性在IE中什么也没做,但是....
metamit 2012年

@EvilClosetMonkey嗨...这个问题有很多见解,我相信futurend在下面的回答比我的要有用。因此,也许考虑将接受的答案更改为futurend的答案,以便使其看起来更高。
metamit


4

我在代码中实现了futurend的解决方案,并且可以正常工作,但是由于我使用了大量的元素,因此速度非常慢。这是使用jQuery的另一种方法,对于我的特定可视化效果更快。它依赖于您想要在顶部具有共同类的svg(在我的示例中,该类在我的数据集中标记为d.key)。在我的代码中,有一个<g>带有“位置”类的类,其中包含我要重新组织的所有SVG。

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

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


4

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

解决元素顺序问题的另一种方法是在绘制时使用“插入”而不是“附加”。这样,路径将始终放置在其他svg元素之前(这假设您的代码已经在其他svg元素的enter()之前执行了link()的链接)。

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


1

我花了很长时间才找到如何在现有SVG中调整Z顺序的方法。在带有工具提示行为的d3.brush上下文中,我确实需要它。为了使这两个功能很好地协同工作(http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html),您需要d3.brush成为Z顺序中的第一个(第一个绘制在画布上,然后被其余SVG元素覆盖),它将捕获所有鼠标事件,无论其上方是什么(Z索引较高)。

大多数论坛评论都说,您应该先在代码中添加d3.brush,然后再添加SVG“绘图”代码。但是对我来说,这是不可能的,因为我加载了一个外部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

希望这可以帮助!


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;
}

而不是使用的:hoverpseudoselector,我使用的是.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树中删除,然后将其添加为其父级的最后一个子级。


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();
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.