使用 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.id
和d.id
时它不起作用。解决方法是直接比较a
和d
。
这对于大量子元素来说表现不佳。最好在随后的 <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 元素调用自定义函数