如何与重叠形状的d3.js鼠标事件进行交互?

时间:2016-04-02 05:51:58

标签: javascript css d3.js svg mouseover

我使用d3.js根据列表myList中的数据在SVG容器上绘制一些绿色圆圈。

以下是该圈子的一个例子:

enter image description here

现在我想实现以下行为:

  1. 当用户的鼠标经过圆圈时,应出现一个矩形。
  2. 矩形的左上角应该是圆的中心。
  3. 当且仅当 鼠标位于圆圈 的边框之外时,矩形才会消失 矩形。
  4. 以下是我为解决此问题而编写的代码(@ Cyril的帮助,谢谢!)。但它不能正常工作。当鼠标指针悬停在圆圈上时,矩形是可见的。但是,当鼠标指针向东南方向移动到矩形中时(即使矩形的一部分与圆的象限重叠),圆形的mouseout事件也会触发,矩形消失 - 甚至在之前矩形的mouseover事件还没有发生。从技术上讲,我认为这仍然是在圈子里。但显然d3.js没有。

    那么,鉴于这些鼠标事件的复杂性以及伴随它们的微小差异(以及竞争条件),我该如何实现此功能呢?

    enter image description here

    var myList = [
      {"centerX": 200, "centerY": 300, "mouseIn": {"circle":false, "rectangle":false}}, 
      {"centerX": 400, "centerY": 500, "mouseIn": {"circle":false, "rectangle":false}}, 
    ];
    
    var myCircle = self.svgContainer.selectAll(".dots")
      .data(myList).enter().append("circle")
      .attr("class", "dots")
      .attr("cx", function(d, i) {return d.centerX})
      .attr("cy", function(d, i) {return d.centerY})
      .attr("r", 15)
      .attr("stroke-width", 0)
      .attr("fill", function(d, i) {return "Green"})
      .style("display", "block");
    
    myCircle.on({
        "mouseover": function(d) {
          console.log('\n\nCircle MouseOver ******************************************');
          var wasCursorIn = d.mouseIn.circle || d.mouseIn.rectangle;
          console.log('wasCursorIn = ', JSON.stringify(wasCursorIn));
          d.mouseIn.circle = true;
          console.log('d.mouseIn = ', JSON.stringify(d.mouseIn));
          var isCursorIn = d.mouseIn.circle || d.mouseIn.rectangle;
          console.log('isCursorIn = ', isCursorIn);
          if ((!wasCursorIn) && isCursorIn) {
            if (typeof d.rectangle === 'undefined' || d.rectangle === null)
              d.rectangle = self.svgContainer.append("rect")
                .attr("x", d.centerX)
                .attr("y", d.centerY)
                .attr("width", 100)
                .attr("height", 50)
                .attr("stroke-width", 2)
                .attr("fill", "DimGray")
                .attr("stroke", "DarkKhaki")
                .on("mouseover", function(e) {
                    console.log('\n\nRectangle MouseOver ***************************************');
                    console.log("d = ", JSON.stringify(d));
                    d.mouseIn.rectangle = true;
                    console.log("d = ", JSON.stringify(d));
                  }
                )
                .on("mouseout", function(e) {
                    console.log('\n\nRectangle MouseOut ****************************************');
                    console.log("d = ", JSON.stringify(d));
                    var wasCursorOut2 = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
                    console.log('wasCursorOut2 = ', wasCursorOut2);
                    d.mouseIn.rectangle = false;
                    console.log('d.mouseIn = ', JSON.stringify(d.mouseIn));
                    var isCursorOut2 = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
                    console.log('isCursorOut2 = ', isCursorOut2);
                    if ((!wasCursorOut2) && isCursorOut2) {
                      d3.select(this).style("cursor", "default");
                      d.rectangle.remove();
                      d.rectangle = null;
                    }
                  }
                )
                .style("display", "block");
            else
              d.rectangle.style("display", "block");
          }
        },
        "mouseout": function(d) {
          console.log('\n\nCircle MouseOut *******************************************');
          var wasCursorOut = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
          console.log('wasCursorOut = ', wasCursorOut);
          d.mouseIn.circle = false;
          console.log('d.mouseIn = ', JSON.stringify(d.mouseIn));
          var isCursorOut = (!d.mouseIn.circle) && (!d.mouseIn.rectangle);
          console.log('isCursorOut = ', isCursorOut);
          if ((!wasCursorOut) && isCursorOut) {
            if (!(typeof d.rectangle === 'undefined' || d.rectangle === null))
              d.rectangle.style("display", "none");
          }
        }
      }
    );
    

1 个答案:

答案 0 :(得分:1)

当SVG元素重叠时,鼠标事件将触发最顶层元素。当鼠标从一个元素移动到另一个元素时,事件的顺序是mouseout事件(对于鼠标离开的元素),然后是mouseover事件(对于鼠标正在进入的元素)。由于您只想在鼠标同时离开circle和rect元素时删除rect元素,因此您需要在circle和rect元素上监听mouseout事件,并且只在鼠标位置位于两者之外时才删除rect元素元件。

以下是确定鼠标位置是否在元素内的一种可能的解决方案。使用svg的getScreenCTM()。inverse()矩阵将鼠标事件的客户端坐标转换为svg坐标。使用该点构造1x1矩阵。使用svg的checkIntersection()来确定矩形是否与元素相交。

以下代码段以简单的javascript(即没有D3.js)演示了这个解决方案。



var svgNS = "http://www.w3.org/2000/svg";

var svg = document.getElementById("mySvg");
var circle = document.getElementById("myCircle");
var rect = null;        

circle.addEventListener("mouseover", circle_mouseover);
circle.addEventListener("mouseout", circle_mouseout);

function circle_mouseover(e) {
    if (!rect) {
        rect = document.createElementNS(svgNS, "rect");
        rect.setAttribute("x", circle.getAttribute("cx"));
        rect.setAttribute("y", circle.getAttribute("cy"));
        rect.setAttribute("width", 100);
        rect.setAttribute("height", 50);
        rect.setAttribute("style", "fill: gray;");
        rect.addEventListener("mouseout", rect_mouseout);
        svg.appendChild(rect);
    }    
}

function circle_mouseout(e) {
    console.log("circle_mouseout");
    if (rect) {
        var p = svg.createSVGPoint();
        p.x = e.clientX;
        p.y = e.clientY;
        p = p.matrixTransform(svg.getScreenCTM().inverse());
        var r = svg.createSVGRect();
        r.x = p.x;
        r.y = p.y;
        r.width = 1;
        r.height = 1;
        if(!svg.checkIntersection(rect, r)) {
            rect.removeEventListener("mouseout", rect_mouseout);
            svg.removeChild(rect);
            rect = null;
        }
    }
}

function rect_mouseout(e) {
    var p = svg.createSVGPoint();
    p.x = e.clientX;
    p.y = e.clientY;
    p = p.matrixTransform(svg.getScreenCTM().inverse());
    var r = svg.createSVGRect();
    r.x = p.x;
    r.y = p.y;
    r.width = 1;
    r.height = 1;
    if(!svg.checkIntersection(circle, r)) {
        rect.removeEventListener("mouseout", rect_mouseout);
        svg.removeChild(rect);
        rect = null;
    }
}

<svg id="mySvg" width="150" height="150">
    <circle id="myCircle" cx="50" cy="50" r="25" style="fill: green;"/>
</svg>
&#13;
&#13;
&#13;

注意:我认为FireFox尚未实现checkIntersection()函数。如果您需要支持FireFox,那么您将需要一种不同的方法来检查点和元素的交集。如果你只处理圆形和矩形,那么很容易编写自己的函数来检查交集。