使用 D3 更新 SVG 元素 Z-Index

Posted

技术标签:

【中文标题】使用 D3 更新 SVG 元素 Z-Index【英文标题】:Updating SVG Element Z-Index With D3 【发布时间】:2012-11-15 17:16:40 【问题描述】:

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

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

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 是一个选项,但我不清楚如何实现它以将“选定”元素置于顶部。

更新:

我使用以下代码修复了我的特殊情况,该代码在 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();
    );

【问题讨论】:

【参考方案1】:

正如其他答案中所解释的,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 将显示所有节点下方的所有链接,无论添加或删除元素的频率和顺序如何。当然,所有相同类型的元素(即在同一个容器中)仍将遵循它们添加的顺序。

【讨论】:

当我真正的问题是将我的元素组织成组时,我脑海中的想法是我可以向上移动一个项目。如果您的“真正”问题是移动单个元素,那么上一个答案中描述的排序方法是一个不错的方法。 太棒了!谢谢你,notan3xit,你拯救了我的一天!只是为其他人添加一个想法 - 如果您需要以与所需可见性顺序不同的顺序添加元素,您可以按正确的可见性顺序创建组(甚至在向它们添加任何元素之前),然后将元素附加到这些按任意顺序分组。 这是正确的方法。一想到就很明显。 对我来说, svg.select('#links') ... 必须替换为 d3.select('#links')... 才能工作。也许这会对其他人有所帮助。【参考方案2】:

开发人员提出的解决方案之一是:“使用 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
  );
)

【讨论】:

这不仅适用于 Z 顺序,它也不会扰乱刚开始的任何拖动元素。 它对我也不起作用。 a,b 似乎只是数据,而不是 d3 选择元素或 DOM 元素 确实,就像@nkint 一样,在比较a.idd.id 时它不起作用。解决方法是直接比较ad 这对于大量子元素来说表现不佳。最好在随后的 <g> 中重新渲染凸起的元素。 另外,svg:use 元素可以动态指向被提升的元素。解决方案见***.com/a/6289809/292085。【参考方案3】:

由于 SVG 没有 Z-index 而是使用 DOM 元素的顺序,您可以通过以下方式将其置于最前面:

this.parentNode.appendChild(this);

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

DEMO:看看这个JSFiddle

【讨论】:

如果不是 Firefox 无法呈现任何 :hover 样式,这将是一个很好的解决方案。我也不认为这个用例需要最后的 ìinsert 函数。 @swenedo,您的解决方案对我来说非常有用,并将元素带到了前面。关于把它带到后面,我有点堆积,不知道如何正确地写它。请您发布一个示例,如何将对象放在后面。谢谢。 @MikeB。当然,我刚刚更新了我的答案。我意识到 d3 的 insert 函数可能不是最好的方法,因为它创建了一个 new 元素。我们只想移动现有元素,因此我在此示例中使用insertBefore 我尝试使用这种方法,但不幸的是我在IE11中不起作用。你有这个浏览器的解决方法吗?【参考方案4】:

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

据我所知(过去我已经尝试过几次),D3 没有提供分离和重新附加单个元素的方法,以便将其放在前面或诸如此类。

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

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

【讨论】:

请注意,在某些浏览器中删除/插入鼠标悬停处理程序中的元素存在问题,这可能导致鼠标悬停事件无法正确发送,因此我建议在所有浏览器中尝试这样做确保它按预期工作。 改变顺序不会改变我的饼图的布局吗?我还在研究 jQuery 以了解如何重新插入元素。 我已经用我挑选的解决方案更新了我的问题,这实际上并没有利用原始问题的核心。如果您发现此解决方案有任何不好的地方,我欢迎 cmets!感谢您对原始问题的见解。 似乎是一种合理的方法,主要是因为覆盖的形状没有填充。如果确实如此,我希望它会在鼠标事件出现的那一刻“窃取”它。而且,我认为@ErikDahlström 的观点仍然成立:这在某些浏览器中可能仍然是一个问题(主要是 IE9)。为了额外的“安全”,您可以将.style('pointer-events', 'none') 添加到覆盖路径。不幸的是,这个属性在 IE 中没有任何作用,但仍然...... @EvilClosetMonkey 嗨......这个问题得到了很多意见,我相信下面futurend的回答比我的更有帮助。因此,也许考虑将接受的答案更改为 futurend's,这样它看起来更高。【参考方案5】:

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

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

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

大多数论坛 cmets 说您应该首先在您的代码中添加 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

希望这会有所帮助!

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:

版本 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 类添加到我的路径中。在鼠标移出时,我将其删除。 与第一种情况一样,代码也会从 DOM 树中删除路径,然后将其添加为其父级的最后一个子级。

【讨论】:

以上是关于使用 D3 更新 SVG 元素 Z-Index的主要内容,如果未能解决你的问题,请参考以下文章

使用 AngularJs 和 D3.js 从 svg 元素调用自定义函数

如何访问与 D3 SVG 对象相关的 DOM 元素?

Renderer2 在使用 d3 js - Angular 4 时不渲染 SVG 元素

d3创建多个svg元素

D3.js:“即时”向数组添加的元素不会刷新 svg 图形

如何使用 D3.js 将图像添加到 svg 容器