【问题标题】:Updating SVG Element Z-Index With D3使用 D3 更新 SVG 元素 Z-Index
【发布时间】: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();
    });

【问题讨论】:

    标签: javascript svg d3.js


    【解决方案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 元素可以动态指向被提升的元素。解决方案见stackoverflow.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 树中删除路径,然后将其添加为其父级的最后一个子级。

              【讨论】:

                猜你喜欢
                • 2014-07-25
                • 2013-07-21
                • 2013-04-26
                • 1970-01-01
                • 2016-03-04
                • 2019-11-17
                • 2013-04-06
                • 1970-01-01
                相关资源
                最近更新 更多