Imas
Imas

Reputation: 177

Performing double mouseup events with other element in d3

I am trying to fire a mouseup event on my node for the surrounding arcs that appear upon mousedown.

My intended functionality is to be able to hold down on a node and drag it into an arc, firing the arc's as well as the node's mouseup event. I am having trouble with my current code, referenced in the JSFiddle below, and would appreciate your help!

For my arcs, I have the following code which I hope to be executed upon mouseup when letting go of the mouse above the particular arc:

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

I would also hope to run the node's mouseup after having dragging the node atop the particular arc and releasing the mouse button:

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

Thank you!

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

Upvotes: 3

Views: 252

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38161

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

Upvotes: 1

Related Questions