Reputation: 187
Objective: Move the red rectangle within the group of black rectangles. Black rectangles form a figure which is restricted to move the red rectangle.
window.onload=function(){
var inter = false;
//Make an SVG Container
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 600);
//draw some rects
var r1 = svgContainer.append("rect")
.attr("class", "interactive")
.attr("x", 10)
.attr("y", 223)
.attr("width", 50)
.attr("height", 150);
var r2 = svgContainer.append("rect")
.attr("class", "interactive")
.attr("x", 223)
.attr("y", 10)
.attr("width", 50)
.attr("height", 300)
.attr("transform", "rotate(45 220,10)");
//group of elements for limit red rect drag
var interactive = d3.selectAll(".interactive")
.on("mouseover", function(d){
inter = true;
})
.on("mouseleave", function(d){
inter = false;
});
// dragging function
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
if(inter){
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
});
}
})
.on("dragstart", function() {
d3.select(this).style("pointer-events", "none")
})
.on("dragend", function() {
d3.select(this).style("pointer-events", "auto")
});
// red rectangle for draging
var r = svgContainer.append("rect")
.attr("x", 150)
.attr("y", 100)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "red")
.data([ {"x":0, "y":0} ])
.call(drag);
}
http://codepen.io/anon/pen/pjorBb
Here's my example of a subject. But it is not working properly. Maybe someone has a similar example of a correct or give clue how to do it correctly.
Upvotes: 6
Views: 2275
Reputation: 187
For example. I changed the task for my own needs. And wrote a decision that might be useful to someone. Next is the code in which a rectangle can only be moved along the line. The lines can be hidden with a transparent color (.attr("fill", "rgba(0, 0, 0, 0)");) and you can draw anything else for user.
http://codepen.io/anon/pen/pjoGOr
var deltaMax = 15; // max distance to line
var svgx = 0;
var svgy = 0;
var limiters = []; //array of lines for align
var svgContainer = d3.select("body").append("svg")
.attr("width", 700)
.attr("height", 700);
var line1 = svgContainer.append("line")
.style("stroke", "black")
.attr("x1", 100)
.attr("y1", 50)
.attr("x2", 100)
.attr("y2", 200);
var line2 = svgContainer.append("line")
.style("stroke", "blue")
.attr("x1", 100)
.attr("y1", 50)
.attr("x2", 300)
.attr("y2", 50);
var line3 = svgContainer.append("line")
.style("stroke", "green")
.attr("x1", 100)
.attr("y1", 50)
.attr("x2", 300)
.attr("y2", 200);
limiters.push(line1);
limiters.push(line2);
limiters.push(line3);
function distance2points(x1,y1,x2,y2){ //distance between 2 points
return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
//find nearest intersection point
function stick(x1,y1,x2,y2,x3,y3) {
//intersection point
var ipoint = { x:0, y:0 };
var onSegment = false;
if((x2 - x1) == 0){
ipoint.x = x1;
ipoint.y = y3;
}else if((y2-y1) == 0){
ipoint.x = x3;
ipoint.y = y1;
}else{
var k = (y2-y1)/(x2-x1);
var b = y2-k*x2;
var kp = -1/k;
var bp = y3-kp*x3;
ipoint.x = (bp-b)/(k-kp);
ipoint.y = ipoint.x*k+b;
}
//xxx helper
point.attr("cx",ipoint.x);
point.attr("cy",ipoint.y);
if(distance2points(x3,y3,ipoint.x,ipoint.y) > deltaMax){
return false;
}
//intersectionn point on segment?
if( (( x1 >= ipoint.x ) && ( x2 <= ipoint.x ) || ( x1 <= ipoint.x ) && ( x2 >= ipoint.x )) &&
(( y1 >= ipoint.y ) && ( y2 <= ipoint.y ) || ( y1 <= ipoint.y ) && ( y2 >= ipoint.y )) ){
onSegment = true;
}else if(distance2points(x1,y1,ipoint.x,ipoint.y) < deltaMax){
ipoint.x = x1;
ipoint.y = y1;
onSegment = true;
}else if(distance2points(x2,y2,ipoint.x,ipoint.y) < deltaMax){
ipoint.x = x2;
ipoint.y = y2;
onSegment = true;
}else{
onSegment = false;
}
if(onSegment){
point.attr("fill","blue");
return ipoint;
}else{
point.attr("fill","red");
return false;
}
}
//mouse position
svgContainer.on('mousemove', function () {
svgx = d3.mouse(this)[0];
svgy = d3.mouse(this)[1];
});
// dragging function
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
for (i = 0; i < limiters.length; i++) {
var obj = limiters[i];
var p = stick(
obj.attr("x1"),
obj.attr("y1"),
obj.attr("x2"),
obj.attr("y2"),
svgx,//mouse position
svgy
);
if(p !== false){
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ p.x,p.y ] + ")"
});
break;
}
}
});
var r = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "red")
.data([ {"x":0, "y":0} ])
.call(drag);
//xxx helper - nearest intersection point
var point = svgContainer.append("circle")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.attr("fill", "green");
Upvotes: 0
Reputation: 14591
This is not a perfect solution. But you may get the idea of how to implement the functionality from this demo.
//Make an SVG Container
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 600);
//draw some rects
var r1 = svgContainer.append("rect")
.attr("class", "interactive")
.attr("x", 10)
.attr("y", 223)
.attr("width", 50)
.attr("height", 150);
var r2 = svgContainer.append("rect")
.attr("class", "interactive")
.attr("x",223)
.attr("y", 10)
.attr("width", 50)
.attr("height", 300)
.attr("transform", "rotate(45 220,10)");
function pointRectangleIntersection(p, r) {
return p.x >= r.x1 && p.x <= r.x2 && p.y >= r.y1 && p.y <= r.y2;
}
// dragging function
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
var pt1 = d3.mouse(r1.node());
var point1 = {x: pt1[0], y: pt1[1]};
var bbox1 = r1.node().getBBox();
var rect1 = { x1: bbox1.x, x2: bbox1.x+bbox1.width, y1: bbox1.y, y2: bbox1.y+bbox1.height };
var pt2 = d3.mouse(r2.node());
var point2 = {x: pt2[0], y: pt2[1]};
var bbox2 = r2.node().getBBox();
var rect2 = { x1: bbox2.x, x2: bbox2.x+bbox2.width, y1: bbox2.y, y2: bbox2.y+bbox2.height };
if(pointRectangleIntersection(point1, rect1) || pointRectangleIntersection(point2, rect2)){
if(pointRectangleIntersection(point1, rect1)){
d.x = Math.max(0, Math.min(rect1.x2 - 20, d3.event.x));
d.y = Math.max(0, Math.min(rect1.y2 - 20, d3.event.y));
} else{
d.x = Math.max(0, Math.min(rect2.x2 - 20, d3.event.x));
d.y = Math.max(0, Math.min(rect2.y2 - 20, d3.event.y));
}
d3.select(this).attr("x", d.x);
d3.select(this).attr("y", d.y);
d3.event.sourceEvent.stopPropagation();
}
});
// red rectangle for draging
var r = svgContainer.append("rect")
.attr("x", 150)
.attr("y", 100)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "red")
.datum({"x":0, "y":0})
.call(drag);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Upvotes: 1
Reputation: 4849
I have came up with a similar situation in the past, This is how I overcame the issue.
When I drag the small element I use a getXY()
function to determine the x, y of the element being dragged.
getXY()
will take the mouse coordinates ( d3.mouse(this)
) and x, y , width, height
of the inner & outer objects. Then it will figure out whether to return the mouse coordinates or not (in this case border coordinates of the outer object).
I hope you will get the idea. I think you can use it to solve your problem.
This is my original post. d3js transforming nested group images
Upvotes: 1