首页 > 解决方案 > 与 d3 中的其他元素执行双 mouseup 事件

问题描述

我正在尝试在我的节点上为 mousedown 时出现的周围弧线触发 mouseup 事件。

我的预期功能是能够按住节点并将其拖动到弧中,触发弧以及节点的 mouseup 事件。我在使用下面的 JSFiddle 中引用的当前代码时遇到问题,不胜感激!

对于我的弧线,我有以下代码,当放开特定弧线上方的鼠标时,我希望在 mouseup 时执行这些代码:

slices.append('path')
    ...
    .on('mouseup', function(arc) {
        console.log(arc.data.title)
    }
);

我还希望在将节点拖动到特定弧的顶部并释放鼠标按钮后运行节点的 mouseup:

svg.append("g")
        .attr("class", "node")
        .selectAll(".bubble")
          .data(data, function(d){ return d.id; })
        .enter().append("circle")
        ...
        .on('mousedown', nodeClickedArcs)
        .on('mouseup', removeArcs)

谢谢!

http://jsfiddle.net/uas6zr7y/3/

标签: javascriptd3.jsdata-visualizationpie-chart

解决方案


The key challenge here is that the mouse events are essentially absorbed by the circle. You could perhaps order the elements in such a manner that the a parent g of the arcs holds a child g with the circles to allow events to propagate up to a relevant parent, but then your circles are underneath your arcs.

One option I'll demonstrate below is to strip pointer interaction from the node during the drag. This won't interrupt the drag (tested on Chrome, Firefox, IE), but will allow the mouse to interact with other elements for mouse over / mouse out events, for example. Then we can listen for the mouse to enter (and exit) a target shape, if the drag ends (mouse up) on a target shape we can trigger a specific action.

This requires all event logic for mouse-up to be located in the drag end function, but requires listeners for mouse enter/exit for the target shape(s).

Based on the above, in the below snippet I:

  1. Strip pointer events from the dragged node (circles) on drag.
  2. Listen if the mouse moves over a target shape (rectangles) and update that element's datum to reflect its "active" status. Conversely, if the mouse moves off that target shape I set the node to be not active.
  3. If a drag event ends while a target shape is active, then I know that my dragged node is over the target shape (and mouse up has occurred) and can act accordingly. If the drag event ends with no active node, then I restore pointer events to the node.

In the snippet below I remove nodes (circles) if they are over a target shape (rectangles), updating the rectangles color in the process:

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300);

var drag = d3.drag()
  .on("drag",dragged)
  .on("end", dragEnd);

var squares = svg.selectAll("rect")
  .data([{x:30},{x:130},{x:230},{x:330},{x:430}])
  .enter()
  .append("rect")
  .attr("x", function(d) { return d.x; })
  .attr("y", 120)
  .attr("width", 40)
  .attr("height",40)
  .attr("fill","white")
  .on("mouseenter", function(d) { d.active = true; })
  .on("mouseout", function(d) { d.active = false; });
  
var circles = svg.selectAll("circle")
  .data(d3.range(20))
  .enter()
  .append("circle")
  .attr("cx", function(d) { return d/21*500 + 25; })
  .attr("cy", function(d) { return d%2 * 40 + 50; })
  .attr("r", 10)
  .attr("fill",  function(d) { return d3.schemeCategory20[d]; })
  .call(drag);
  

function dragged(d) {
  var x = d3.mouse(this)[0];
  var y = d3.mouse(this)[1];
  
  d3.select(this)
    .attr("cx",x)
    .attr("cy",y)
    .style("pointer-events","none");
}

function dragEnd(d) {
  var rect = squares.filter(function(d) {
    return d.active;
  })
  if(!rect.empty()) {
    var circle = d3.select(this);
    rect.attr("fill",interpolateColor(rect,circle));
    circle.remove();
  }
  else {
    d3.select(this).style("pointer-events","all");
  }
}

function interpolateColor(r,c) {
 return d3.interpolateLab(r.attr("fill"),c.attr("fill"))(0.5)
}
svg {
  stroke: black;
  stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

This checks the mouse position, not if the circle overlaps the rectangle, collision detection would require a different approach


推荐阅读