ben
ben

Reputation: 137

D3js drag&drop an object and detect where it is dropped.

I am working on dragging and dropping svg using d3js. There are two problems and I think they are related to each other.

  1. When the circle is dropped it has to detect that it was dropped into the rectangle. Some of the examples that I have looked at uses x and y coordinates of the mouse, but I don't fully understand it.

  2. Another problem is that the circle appears behind the rectangle. Is there a way to bring it to the front when the circle is moving around without changing the order of where the circle and rectangle are created i.e(create circle first and then rectangle).

var width = window.innerWidth,
  height = window.innerHeight;

var drag = d3.behavior.drag()
  .on("dragstart", dragstarted)
  .on("drag", dragged)
  .on("dragend", dragended);

//create circle and space evenly
var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var circle = d3.select("svg")
  .append("circle")
  .attr("cx", 50)
  .attr("cy", 30)
  .attr("r", 15)
  .attr("transform", "translate(0,0)")
  .style("stroke", "black")
  .call(drag);


function dragstarted(d) {
  d3.event.sourceEvent.stopPropagation();
}

function dragged(d) {
  d3.select(this).attr("transform", "translate(" + [d3.event.x, d3.event.y] + ")");
}

function dragended(d) {
  d3.event.sourceEvent.stopPropagation();

  // here would be some way to detect if the circle is dropped inside the rect. 
  
}

var ellipse = svg.append("rect")
      .attr("x", 150)
      .attr("y", 50)
      .attr("width", 50)
      .attr("height", 140)
      .attr("fill", "green");
<script src="https://d3js.org/d3.v3.min.js"></script>

Any help is appreciated.

Upvotes: 0

Views: 732

Answers (1)

Nick
Nick

Reputation: 16576

Updated to still include the bounding client rectangle, but iterate through any number of rectangles that exist. New Fiddle here.

Here's my solution to the problem. I used a great little "moveToBack" helper function seen here to move the rect to the back without changing the order in which it appears.

To get the positions of the circle and rectangle, I made heavy use of the vanilla js getBoundingClientRect() method. You can see all this together in this JS Fiddle.

var width = window.innerWidth,
  height = window.innerHeight;

var drag = d3.behavior.drag()
  .on("dragstart", dragstarted)
  .on("drag", dragged)
  .on("dragend", dragended);

//create circle and space evenly
var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var circle = d3.select("svg")
  .append("circle")
  .attr("r", 15)
  .attr("transform", "translate(50,30)")
  .style("stroke", "black")
  .attr("id", "circle")
  .call(drag);


d3.selection.prototype.moveToBack = function() {  
  return this.each(function() { 
    var firstChild = this.parentNode.firstChild; 
    if (firstChild) { 
      this.parentNode.insertBefore(this, firstChild); 
    } 
  });
};

var rect = svg.append("rect")
      .attr("x", 150)
      .attr("y", 50)
      .attr("width", 50)
      .attr("height", 140)
      .attr("fill", "green")
      .attr("id", "rect")
      .moveToBack();

var rect2 = svg.append("rect")
      .attr("x", 350)
      .attr("y", 50)
      .attr("width", 50)
      .attr("height", 140)
      .attr("fill", "green")
      .attr("id", "rect")
      .moveToBack();

function dragstarted(d) {
  d3.event.sourceEvent.stopPropagation();
}

function dragged(d) {
  d3.select(this).attr("transform", "translate(" + d3.event.x + "," + d3.event.y + ")");
}

function dragended(d) {
  // Define boundary
  var rects = document.querySelectorAll("rect");
  for (var i = 0; i < rects.length; i++) {
    var rectDimensions = rects[i].getBoundingClientRect();
    var xmin = rectDimensions.x;
    var ymin = rectDimensions.y;
    var xmax = rectDimensions.x + rectDimensions.width;
    var ymax = rectDimensions.y + rectDimensions.height;
    // Get circle position
    var circlePos = document.getElementById("circle").getBoundingClientRect();
    var x1 = circlePos.x;
    var y1 = circlePos.y;
    var x2 = circlePos.x + circlePos.width;
    var y2 = circlePos.y + circlePos.height;
    if(x2 >= xmin && x1 <= xmax && y2 >= ymin && y1 <= ymax) {
      rects[i].setAttribute("fill", "red");
    } else {
      rects[i].setAttribute("fill", "green");
    }  
  }
  d3.event.sourceEvent.stopPropagation();
}

Upvotes: 1

Related Questions