Vasiliy Letuyev
Vasiliy Letuyev

Reputation: 187

Restrict the movement of the element, when we drag it

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

Answers (3)

Vasiliy Letuyev
Vasiliy Letuyev

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

Gilsha
Gilsha

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

Fawzan
Fawzan

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

Related Questions