获取D3节点变换后的屏幕位置

21

我正在尝试在使用d3.behavior.zoom()进行布局转换后获取节点的屏幕位置,但是我没有太多成功。我该如何获取节点在平移和缩放布局后在窗口中的实际位置?

mouseOver = function(node) {
  screenX = magic(node.x); // Need a magic function to transform node
  screenY = magic(node.y); // positions into screen coordinates.
};

希望能得到任何指导。

编辑:上面的“node”是一个力导向图节点,因此它的x和y属性由模拟设置,并在模拟停止后保持不变,无论应用了什么类型的变换。

编辑:我用于转换SVG的策略来自于d3的缩放行为,其在此处概述:SVG几何缩放

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g")
    .call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
  .append("g");

svg.append("rect")
    .attr("class", "overlay")
    .attr("width", width)
    .attr("height", height);

svg.selectAll("circle")
    .data(data)
  .enter().append("circle")
    .attr("r", 2.5)
    .attr("transform", function(d) { return "translate(" + d + ")"; });

function zoom() {
  svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}

很简单。d3的缩放行为将平移和缩放事件传递给处理程序,通过变换属性将变换应用于容器元素。

编辑:我通过使用鼠标坐标而不是节点坐标来解决问题,因为我对当鼠标指针悬停在节点上时的节点位置感兴趣。这不完全是我想要的行为,但它大部分情况下有效,并且比没有好。

编辑:解决方案是使用element.getCTM()获取svg元素的当前变换矩阵,然后使用它来将x和y坐标偏移为相对于屏幕的状态。请参见以下内容。


我怀疑答案可能涉及到对SVG元素基本数据类型的getCTM()和/或getScreenCTM()进行某些操作。我想知道是否有辅助方法可以对这些函数返回的矩阵进行操作,例如Inverse(tm)或类似的东西。 - t.888
2个回答

26

看起来解决我最初的问题的方法大致如下:

(已更新以支持旋转变换。)

// The magic function.
function getScreenCoords(x, y, ctm) {
    var xn = ctm.e + x*ctm.a + y*ctm.c;
    var yn = ctm.f + x*ctm.b + y*ctm.d;
    return { x: xn, y: yn };
}

var circle = document.getElementById('svgCircle'),
cx = +circle.getAttribute('cx'),
cy = +circle.getAttribute('cy'),
ctm = circle.getCTM(),
coords = getScreenCoords(cx, cy, ctm);
console.log(coords.x, coords.y); // shows coords relative to my svg container

此外,如果不需要旋转变换,也可以使用d3.event的translate和scale属性来完成:

// This function is called by d3's zoom event.
function zoom() {

// The magic function - converts node positions into positions on screen.    
function getScreenCoords(x, y, translate, scale) {
    var xn = translate[0] + x*scale;
    var yn = translate[1] + y*scale;
    return { x: xn, y: yn };
}

// Get element coordinates and transform them to screen coordinates.
var circle = document.getElementById('svgCircle');
cx = +circle.getAttribute('cx'),
cy = +circle.getAttribute('cy'),
coords = getScreenCoords(cx, cy, d3.event.translate, d3.event.scale);
console.log(coords.x, coords.y); // shows coords relative to my svg container

  // ...
}

编辑:我发现以下函数形式最有用和通用,而且它似乎可以解决getBoundingClientRect的缺陷。更具体地说,在尝试获取D3 force布局项目中准确的SVG节点位置时,getBoundingClientRect产生了不准确的结果,而下面的方法返回了circle元素在多个浏览器上的精确中心坐标。

(更新以支持旋转变换。)

// Pass in the element and its pre-transform coords
function getElementCoords(element, coords) {
    var ctm = element.getCTM(),
    x = ctm.e + coords.x*ctm.a + coords.y*ctm.c,
    y = ctm.f + coords.x*ctm.b + coords.y*ctm.d;
    return {x: x, y: y};
};

// Get post-transform coords from the element.
var circle = document.getElementById('svgCircle'),
x = +circle.getAttribute('cx'),
y = +circle.getAttribute('cy'),
coords = getElementCoords(circle, {x:x, y:y});

// Get post-transform coords using a 'node' object.
// Any object with x,y properties will do.
var node = ..., // some D3 node or object with x,y properties.
circle = document.getElementById('svgCircle'),
coords = getElementCoords(circle, node);

该函数的工作原理是获取DOM元素的变换矩阵,然后使用矩阵旋转、缩放和平移信息来返回给定节点对象的变换后坐标。


1
非常感谢,这让我的一天都变得美好了。我在网上搜索了几个小时才找到解决方案。 - Mr. Sam
1
嗯,你能确认一下吗?当应用旋转时,这个函数仍然无法工作,对吧?因为你没有使用ctm.b和ctm.c来转换坐标。 - Overdrivr
有人知道如何将旋转变换应用于getElementCoords()函数吗? - poliu2s
@poliu2s,如果我没记错的话,你需要使用xn = ctm.e + coords.x*ctm.a + coords.y*ctm.byn = ctm.f + coords.x*ctm.c + coords.y*ctm.d。如果这个方法可行,请告诉我,我会更新答案。 - t.888
1
@poliu2s,看起来我在之前的评论中旋转元素是反过来的。这应该可以解决问题:x = ctm.e + coords.x*ctm.a + coords.y*ctm.cy = ctm.f + coords.x*ctm.b + coords.y*ctm.d - t.888
2
感谢您添加旋转变换的代码。已经生效。 - akauppi

9
您可以尝试使用node.getBBox()来获取应用任何transform后节点形状周围紧密边界框的像素位置。更多信息请参见这里:链接
编辑: getBBox的工作方式并不完全符合我的想法。由于矩形是以变换坐标空间定义的,因此它始终相对于父级<g>而言,并且对于包含的形状始终相同。
还有另一个名为element.getBoundingClientRect的函数,似乎得到了广泛支持,并且它返回其矩形在与浏览器视口左上角的像素位置相关联。这可能会让您更接近所需,而无需直接处理变换矩阵。

谢谢提供链接。文档明确表示getBBox()是后变换的,但我得到的node.x和node.y的坐标与el.getBBox().x和el.getBBox().y相同。需要注意的是,'node'是选择数据中的力布局节点。 - t.888
在调用getBBox时应用于元素的变换是什么? - Scott Cameron
我编辑了我的帖子,试图回答你的问题。我正在通过容器的变换属性应用翻译和缩放变换。SVG几何缩放 - t.888
如果您在Chrome开发工具的元素选项卡中查看,元素的transform属性的实际值是什么?它是否与getBBox告诉您的相符? - Scott Cameron
变换实际上是应用于父级的 'svg:g' 元素。然而,检查 svg 节点的 cx 和 cy 值表明,当布局被转换时,这些值不会改变,并且这些值与 getBBox() 返回的值一致。 - t.888
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接