D3 - SVG元素上的定位工具提示不起作用

时间:2014-01-16 03:55:44

标签: svg d3.js position tooltip

我有一个带有SVG的网页。在某些形状上,我需要显示工具提示。但是,我无法将工具提示显示在它应该的位置,只是距离形状本身一些像素。

它出现在屏幕的右侧,可能距离大约300px。  我用来获取坐标的代码如下:

d3.select("body")
  .select("svg")
  .select("g")
  .selectAll("circle")
  .on("mouseover", function(){return tooltip.style("visibility", "visible");})
  .on("mousemove", function(){

var svgPos = $('svg').offset(),

/*** Tooltip ***/

//This should be the correct one, but is displaying at all working at all.
/*x = svgPos.left + d3.event.target.cx.animVal.value,
y = svgPos.top + d3.event.target.cy.animVal.value;*/


//This displays a tool tip but way much to the left of the screen.
x = svgPos.left + d3.event.target.cx.animVal.value,
y = svgPos.top + d3.event.target.cy.animVal.value;

Tooltip
window.alert("svgPos: "+svgPos+"        top: "+y+"px          left: "+x+"px     "+d3.event.target.cx.animVal.value);


return tooltip.style("top", x+"px").style("left",y+"px");
})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});

我在this SO post之后得到了这段代码。

我更改了$(ev.target).attr(cx),因为它没有在我的机器上返回值; d3.event.target.cx是,尽管它似乎并没有影响最终结果。

我做错了什么?有人可以帮帮我吗?非常感谢你的时间。

1 个答案:

答案 0 :(得分:30)

如果您的工具提示是HTML元素,那么您希望将其相对于页面整体定位,而不是内部SVG坐标,因此访问cx / cy值只会使事情变得复杂。我不能在不查看您的代码的情况下肯定地说,但如果您对<svg><g>元素进行了任何转换,那么这可能会让您失望。

然而,有一个更容易的解决方案。只需访问鼠标事件的默认.pageX.pageY属性,这些属性提供鼠标相对于HTML正文的位置,并使用这些坐标来定位工具提示div。

此处示例:http://fiddle.jshell.net/tPv46/1/

密码:

.on("mousemove", function () {
    //console.log(d3.event);
    return tooltip
        .style("top", (d3.event.pageY + 16) + "px")
        .style("left", (d3.event.pageX + 16) + "px");
})

即使在SVG圆圈上进行旋转变换,鼠标也会知道它在页面上的位置,并且相应地定位了工具提示。

还有其他方法可以做到这一点,包括让工具提示显示在相对于圆圈的固定位置而不是跟随鼠标,但我只是检查了我正在处理的示例并意识到它们不是交叉-browser兼容,所以我必须将它们标准化并回复你。与此同时,我希望这能让您重回项目。

编辑1

为了进行比较,以下是使用HTML工具提示(<div>元素)和SVG工具提示(<g>元素)实现的相同示例。

http://fiddle.jshell.net/tPv46/4/

默认鼠标事件坐标可能非常适合定位<body>的直接子元素的HTML元素,但它们对于定位SVG元素不太有用。在应用了所有变换之后,d3.mouse()函数计算当前事件相对于指定SVG元素坐标系的鼠标坐标。因此,它可用于以我们定位SVG工具提示所需的形式获取鼠标坐标。

密码:

.on("mousemove", function () {


    var mouseCoords = d3.mouse(
        SVGtooltip[0][0].parentNode);
    //the d3.mouse() function calculates the mouse
    //position relative to an SVG Element, in that 
    //element's coordinate system 
    //(after transform or viewBox attributes).

    //Because we're using the coordinates to position
    //the SVG tooltip, we want the coordinates to be
    //with respect to that element's parent.
    //SVGtooltip[0][0] accesses the (first and only)
    //selected element from the saved d3 selection object.

    SVGtooltip
        .attr("transform", "translate(" + (mouseCoords[0]-30)
                  + "," + (mouseCoords[1]-30) + ")");

    HTMLtooltip
        .style("top", (d3.event.pageY + 16) + "px")
        .style("left", (d3.event.pageX + 16) + "px");
})

请注意,即使我使用viewBox属性缩放SVG并将工具提示放在带有转换属性的<g>内,它也能正常工作。

在Chrome,Firefox和Opera(合理的最新版本)中经过测试和工作 - 虽然SVG工具提示中的文字可能会超出其矩形,具体取决于您的字体设置。使用HTML工具提示的一个原因!另一个原因是它不会被SVG的边缘切断。

如果您在Safari或IE9 / 10/11中有任何错误,请发表评论。 (IE8及以下版本不幸,因为他们不做SVG)。

编辑2

那么您最初的想法是什么,将工具提示放在圆圈本身上?能够准确定位笔尖有一定的好处:更好的布局控制,文本不会随着鼠标摆动。最重要的是,你可以在mouseover事件中定位一次,而不是对每个mousemove事件作出反应。

但要做到这一点,你不能再只使用鼠标位置来确定放置工具提示的位置 - 你需要弄清楚元素的位置,这意味着你必须处理转换。 SVG规范为locating SVG elements relative to other parts of the DOM引入了一组接口。

要在两个SVG转换系统之间进行转换,请使用SVGElement.getTransformToElement(SVGElement);要在SVG坐标系和屏幕之间进行转换,请使用SVGElement.getScreenCTM()。结果是transformation matrices,您可以从中获得 提取净水平和垂直平移。

SVG工具提示的关键代码是

    var tooltipParent = SVGtooltip[0][0].parentNode;
    var matrix = 
            this.getTransformToElement(tooltipParent)
                .translate(+this.getAttribute("cx"),
                     +this.getAttribute("cy"));
    SVGtooltip
        .attr("transform", "translate(" + (matrix.e)
                  + "," + (matrix.f - 30) + ")");

HTML工具提示的关键代码是

    var matrix = this.getScreenCTM()
            .translate(+this.getAttribute("cx"),
                     +this.getAttribute("cy"));

    absoluteHTMLtooltip
        .style("left", 
               (window.pageXOffset + matrix.e) + "px")
        .style("top",
               (window.pageYOffset + matrix.f + 30) + "px");

实例:http://fiddle.jshell.net/tPv46/89/

同样,我很感激任何可以在Safari或IE或任何移动浏览器中测试此内容的人的确认评论。我很确定我已经为所有内容使用了标准API,但仅仅因为API是标准的并不意味着它是普遍实现的!