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

I am working on dragging and dropping svg using d3js. There are two problems and I think they are related to each other.
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.
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.

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();
}

Related

Drag points with Lat/Lng inputs

I've been using this example to enable dragging of points. Successful JS fiddle here.
My question is, how do I convert this to run off input data which uses a co-ordinate system based on lat/longs?
I can display/project the points fine, but when I drag it, it pins to the top left corner. DevTools Console returns an error "Error: attribute cx: Expected length, "NaN"." Same returned for attribute cy.
I think it's something to do with the dragged function, but all the permutations I've tried on it have failed.
var width = Math.max(960, window.innerWidth),
height = Math.max(500, window.innerHeight) - 90;
var tile = d3.geo.tile()
.size([width, height]);
var projection = d3.geo.mercator()
.scale((1 << 23) / 2 / Math.PI)
.translate([-width / 2, -height / 2]);
var drag = d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var container = d3.select("body").append("div")
.attr("id", "container")
.style("width", width + "px")
.style("height", height + "px");
var points = container.append("svg")
.attr("id", "points");
var nodes_data_latlng = [{ "lat1": -0.01, "lng1": 0.025 }];
drawnodeslatlng();
function drawnodeslatlng() {
d3.select("#points").selectAll("circle")
.data(nodes_data_latlng)
.enter()
.append("circle")
.attr("cx", function (d) { return projection([d.lng1, d.lat1])[0] })
.attr("cy", function (d) { return projection([d.lng1, d.lat1])[1] })
.attr("r", "10")
.call(drag)
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this)
.attr("cx", d.lng1 = d3.event.x)
.attr("cy", d.lat1 = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
<html>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/d3.geo.tile.v0.min.js"></script>
</body>
</html>
In D3 v3, the .origin method you have here...
var drag = d3.behavior.drag()
.origin(function (d) { return d; })
...requires an object with x and y properties. The API for that quite old and outdated version says:
Frequently the origin accessor is specified as the identity function: function(d) { return d; }. This is suitable when the datum bound to the dragged element is already an object with x and y attributes representing its current position.
Therefore, the easiest solution is simply removing it:
var width = Math.max(960, window.innerWidth),
height = Math.max(500, window.innerHeight) - 90;
var tile = d3.geo.tile()
.size([width, height]);
var projection = d3.geo.mercator()
.scale((1 << 23) / 2 / Math.PI)
.translate([-width / 2, -height / 2]);
var drag = d3.behavior.drag()
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var container = d3.select("body").append("div")
.attr("id", "container")
.style("width", width + "px")
.style("height", height + "px");
var points = container.append("svg")
.attr("id", "points");
var nodes_data_latlng = [{ "lat1": -0.01, "lng1": 0.025 }];
drawnodeslatlng();
function drawnodeslatlng() {
d3.select("#points").selectAll("circle")
.data(nodes_data_latlng)
.enter()
.append("circle")
.attr("cx", function (d) { return projection([d.lng1, d.lat1])[0] })
.attr("cy", function (d) { return projection([d.lng1, d.lat1])[1] })
.attr("r", "10")
.call(drag)
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this)
.attr("cx", d.lng1 = d3.event.x)
.attr("cy", d.lat1 = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
<html>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/d3.geo.tile.v0.min.js"></script>
</body>
</html>

Using keydown event with d3.drag () to rotate a SVG

I have a SVG and I want to apply rotation transformation on it but not just with the mouse drag, instead with a mouse drag while pressing the alt key. I have tried to implement something but things donot seem to work as I want. Below is what i have implemented. How can I get the SVG rotated on dragging the mouse while pressing the alt key?
var width = 590, height = 400;
var svg = d3.select("#sketch4").append("svg")
.attr("width", width)
.attr("height", height)
//.call(d3.zoom().on("zoom", zoomed))
.call(d3.drag().on('drag', dragged))
.append("g")
var rect = svg.append("rect")
.attr("x", 100)
.attr("y", 100)
.attr("width", 60)
.attr("height", 30)
.attr("fill", "green")
function dragged() {
if (d3.event.sourceEvent.altKey){
var me = sketchSVG.node()
var x1 = me.getBBox().x + me.getBBox().width/2;
var y1 = me.getBBox().y + me.getBBox().height/2;
sketchSVG.attr("transform","rotate("+d3.event.y+","+x1+","+y1+")" );
metricSVG.attr("transform","rotate("+d3.event.y+","+x1+","+y1+")" );
}
};
You need to use d3.event.sourceEvent.x and d3.event.sourceEvent.y. Changing that should get your code working.
To move/zoom your element, create and else condition and just use sketchSVG.attr("transform",d3.event.transform) for zooming and translating the element.
var width = 590,
height = 400;
var svg = d3.select("#sketch4").append("svg")
.attr("width", width)
.attr('id', 'sketchSvg')
.attr("height", height)
.append("g")
d3.select('#sketchSvg')
.call(d3.zoom().on("zoom", dragged))
var rect = svg.append("rect")
.attr("x", 100)
.attr("y", 100)
.attr("width", 60)
.attr("height", 30)
.attr("fill", "green")
function dragged(d) {
if (d3.event.sourceEvent.altKey && d3.event.sourceEvent.type == 'mousemove') {
var me = svg.node()
var x1 = me.getBBox().x + me.getBBox().width / 2;
var y1 = me.getBBox().y + me.getBBox().height / 2;
svg.attr("transform", "rotate(" + d3.event.sourceEvent.y + "," + x1 + "," + y1 + ")");
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id='sketch4'></div>
Here's one on JSFiddle

D3 transition not working after update

I am trying to make a transition after drawing squares next to already drawn squares in my SVG container. My code to draw new squares next to the already drawn ones is as follow (not working):
var calculation = numToDrawSquares - numDrawnSquares;
var selectedGroup = d3.select("#" + source + "Group");
// Loop to draw the missing squares
for(var j = 0; j < calculation; j++)
{
var draw = selectedGroup.append("rect")
.attr("class", source)
.attr("x", x)
.attr("y", y)
.attr("width", 15)
.attr("height", 15)
.style("fill", color);
draw.transition()
.attr("y", y)
.duration(3000)
.delay(250);
}
My code for the already drawn squares is as follow (working):
// SVG container
container = d3.select("#" + source)
.append("svg")
.attr("class", "svgContainer")
.attr("width", 81)
.attr("height", 600);
rectangleGroup = container.append("g")
.attr("class", source + "_group" + " tooltip")
.attr("id", source + "Group")
// Drawing the squares
rectangle = rectangleGroup.append("rect")
.attr("class", source)
.attr("x", x)
.attr("y", 0)
.attr("width", 15)
.attr("height", 15)
.style("fill", "#" + color)
.transition()
.attr("y", y)
.duration(1000)
.delay(250);
Does anyone know why the transition in my first code snippet is not working? What can I do to fix that?

How to drag and drop a rectangle

Unable to drag rectangle in d3js. The code looks fine to me. What I am I missing here?
All the drag behaviors like start drag and drag end are implemented but still the drag is not working
Here is my code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 803);
var rect = svgContainer.append("rect")
.attr("x", 10)
.attr("y", 50)
.attr("width", 51)
.attr("height", 41)
.attr("stroke", "#7E7E7E")
.attr("cursor", "move")
var drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", drag)
.on("dragend", dragend);
function dragstart()
{
d3.event.sourceEvent.stopPropagation();
}
function drag()
{
var self = d3.select(this);
//var translate =d3.transform(self.getAttribute("transform")).translate;
var translate = d3.transform(self.attr("transform")).translate;
var x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
self.attr("transform", "translate(" + x + "," + y + ")");
}
function dragend()
{
var mouseCoordinates = d3.mouse(this);
if (mouseCoordinates[0] > 170)
{
//Append new element
svg.append("g").append("rect")
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.attr("width", 51)
.attr("height", 41)
.attr("stroke", "#7E7E7E")
.style('cursor', 'move')
.classed("initialDragRect", true)
.call(drag);
}
}
rect.call(drag);
Change the translate variable declaration on your code in function drag into this
var translate = d3.transform(self.attr("transform")).translate;
The problem is because the self variable does not have the function getAttribute()

Appending element clone to svg group in d3js

I am able to append a rectangle to group on drag drop but it is not working properly. If I drag and drop multiple rectangles on to group, the rectangles are dislocated at different location. I am sure there must be some thing wrong.
Demo: http://jsfiddle.net/wqvLLbLa/
code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 803);
var rect = svgContainer.append("rect")
.attr("x", 10)
.attr("y", 50)
.attr("width", 51)
.attr("height", 41)
.attr("rx", 10)
.attr("stroke-width", 2)
.attr("stroke", "#7E7E7E")
.style('cursor', 'move')
.style("fill", "white");
function moveRect() {
d3.select(this)
.attr('x', d3.event.x)
.attr('y', d3.event.y);
}
var dragGroup = d3.behavior.drag()
.origin(function () {
var g = this;
return {x: d3.transform(g.getAttribute("transform")).translate[0],
y: d3.transform(g.getAttribute("transform")).translate[1]};
})
.on("drag", function (d, i) {
g = this;
translate = d3.transform(g.getAttribute("transform")).translate;
console.log(translate);
x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
d3.select(g).attr("transform", "translate(" + x + "," + y + ")");
d3.event.sourceEvent.stopPropagation();
});
var group = svgContainer.append("g")
.attr("id", "mygroup")
.call(dragGroup)
.style('cursor', 'move')
.attr("transform", "translate(20, 20)");
group.append("rect")
.attr("x", 250)
.attr("y", 250)
.attr("width", 151)
.attr("height", 141)
.attr("rx", 10)
.attr("stroke-width", 2)
.attr("stroke", "#7E7E7E")
.style("fill", "white");
var circleDrag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("cx"), y: t.attr("cy")};
})
var rectDrag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on('dragend', function (d) {
var mouseCoordinates = d3.mouse(this);
var groupTransform = d3.transform(group.attr("transform"));
var groupX = groupTransform.translate[0];
var groupY = groupTransform.translate[1];
var rect = group.select("rect");
var rectX = +rect.attr("x");
var rectY = +rect.attr("y");
var rectWidth = +rect.attr("width");
var rectHeight = +rect.attr("height");
if (mouseCoordinates[0] > groupX + rectX
&& mouseCoordinates[0] < groupX + rectX + rectWidth
&& mouseCoordinates[1] > groupY + rectY
&& mouseCoordinates[1] < groupY + rectY + rectHeight) {
//Append new element
var newRect = d3.select("g").append("rect")
.classed("drg", true)
.attr("x", 100)
.attr("y", 100)
.attr("rx", 10)
.attr("width", 51)
.attr("height", 41)
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483");
}
else
{
var newRect = d3.select("svg").append("rect")
.classed("drg", true)
.attr("x", 100)
.attr("y", 100)
.attr("rx", 10)
.attr("width", 51)
.attr("height", 41)
.attr("x", mouseCoordinates[0])
.attr("y", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483")
.call(
d3.behavior.drag()
.on('drag', moveRect).origin(function () {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
}));
}
});
rect.call(rectDrag);
Responding to the update question - as in the comments.
The reason for the duplicates is because you are appending a new element to the targetG after your bounds check, instead of targetCircle.
The easiest way to fix this would be to simply remove targetCircle after appending the new circle like so
targetCircle.remove();
Fiddle - http://jsfiddle.net/afmLhofL/

Categories