89

使用 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两者都不起作用。

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

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

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

我听说 usingsort是一个选项,但我不清楚如何实现将“选定”元素置于顶部。

更新:

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

10 回答 10

93

As explained in the other answers, SVG does not have a notion of a z-index. Instead, the order of elements in the document determines the order in the drawing.

Apart from reordering the elements manually, there is another way for certain situations:

Working with D3 you often have certain types of elements that should always be drawn on top of other types of elements.

For example, when laying out graphs, links should always be placed below nodes. More generally, some background elements usually need to be placed below everything else, while some highlights and overlays should be placed above.

If you have this kind of situation, I found that creating parent group elements for those groups of elements is the best way to go. In SVG, you can use the g element for that. For example, if you have links that should be always placed below nodes, do the following:

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

Now, when you paint your links and nodes, select as follows (the selectors starting with # reference the element 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

Now, all links will always be appended structurally before all node elements. Thus, the SVG will show all links below all nodes, no matter how often and in what order you add or remove elements. Of course, all elements of the same type (i.e. within the same container) will still be subject to the order in which they were added.

于 2013-06-16T12:14:49.127 回答
56

One of the solutions presented by the developer is: "use D3's sort operator to reorder the elements." (see https://github.com/mbostock/d3/issues/252)

In this light, one might sort the elements by comparing their data, or positions if they were dataless elements:

.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
  });
})
于 2012-12-10T02:02:14.147 回答
26

Since SVG doesn't have Z-index but use the order of the DOM elements, you can bring it to front by:

this.parentNode.appendChild(this);

You can then e.g. make use of insertBefore to put it back on mouseout. This however requires you to be able to target the sibling-node your element should be inserted before.

DEMO: Take a look at this JSFiddle

于 2013-06-29T23:22:12.497 回答
13

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

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

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

Or, you could skip d3 for this task and use plain JS (or jQuery) to re-insert that single DOM element.

于 2012-11-28T06:14:00.050 回答
13

The simple answer is to use d3 ordering methods. In addition to d3.select('g').order(), there is .lower() and .raise() in version 4. This changes how your elements appear. Please consult the docs for more information - https://github.com/d3/d3/blob/master/API.md#selections-d3-selection

于 2017-02-02T10:54:11.817 回答
4

I implemented futurend's solution in my code and it worked, but with the large number of elements I was using, it was very slow. Here's the alternative method using jQuery that worked faster for my particular visualization. It relies on the svgs you want on top having a class in common (in my example the class is noted in my data set as d.key). In my code there is a <g> with the class "locations" that contains all of the SVGs I'm re-organizing.

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

So when you hover on a particular data point, the code finds all the other data points with SVG DOM elements with that particular class. Then it detaches and re-inserts the SVG DOM elements associated with those data points.

于 2013-06-21T23:04:59.037 回答
4

Wanted to expand on what @notan3xit answered rather than write out an entire new answer (but I don't have enough reputation).

Another way to solve the element order problem is to use 'insert' rather than 'append' when drawing . That way the paths will always be placed together before the other svg elements(this assumes your code already does the enter() for links before the enter() for the other svg elements).

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

于 2014-04-10T16:27:35.163 回答
1

It took me ages to find how to tweak the Z-order in an existing SVG. I really needed it in the context of d3.brush with tooltip behavior. In order to have the two features work nicely together (http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html), you need the d3.brush to be the first in Z-order (1st to be drawn on the canvas, then covered by the rest of the SVG elements) and it will capture all mouse events, no matter what is on top of it (with higher Z indices).

Most forum comments say that you should add the d3.brush first in your code, then your SVG "drawing" code. But for me it was not possible as I loaded an external SVG file. You can easily add the brush at any time and alter the Z-order later on with:

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

In the context of a d3.brush setup it will look like:

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() function API: https://github.com/mbostock/d3/wiki/Selections#insert

Hope this helps!

于 2014-10-11T02:31:19.310 回答
1

You can Do like this On Mouse Over You can Pull it to top.

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

If You want To Push To Back

nodes.on("mouseout",function(){
   d3.select(this).pushElementAsBackLayer();
});
于 2017-03-24T11:10:34.323 回答
0

Version 1

In theory, the following should work fine.

The CSS code :

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

This CSS code will add a stroke to the selected path.

The JS code :

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

This JS code first removes the path from the DOM tree and then adds it as the last child of its parent. This makes sure the path is drawn on top of all other children of the same parent.

In practice, this code works fine in Chrome but breaks in some other browsers. I tried it in Firefox 20 on my Linux Mint machine and couldn't get it to work. Somehow, Firefox fails to trigger the :hover styles and I haven't found a way to fix this.


Version 2

So I came up with an alternative. It may be a bit 'dirty', but at least it works and it doesn't require looping over all elements (as some of the other answers).

The CSS code :

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

Instead of using the :hover pseudoselector, I use a .hover class

The JS code :

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

On mouseover, I add the .hover class to my path. On mouseout, I remove it. As in the first case, the code also removes the path from the DOM tree and then adds it as the last child of its parent.

于 2014-03-31T20:44:58.290 回答