Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3js: Dragging a group by using one of it's children

Jsfiddle: http://jsfiddle.net/6NBy2/

Code:

var in_editor_drag = d3.behavior.drag()
             .origin(function() {
                var g = this.parentNode;
                return {x: d3.transform(g.getAttribute("transform")).translate[0],
                        y: d3.transform(g.getAttribute("transform")).translate[1]};
            })
            .on("drag", function(d,i) {

                g = this.parentNode;
                translate = d3.transform(g.getAttribute("transform")).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();
            });

svg = d3.select("svg");
d = {x: 20, y: 20 };
groups = svg
        .append("g")
        .attr("transform", "translate(20, 20)");

groups
    .append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("width", 100)
        .attr("height", 100)
        .style("fill", "green")
        .call(in_editor_drag)
        .style("opacity", 0.4);

I'm trying to drag a group by using one of it's children as a handle. Simply, what i'm trying to do is, when a groups child is dragged:

  • Get translation transformation of group
  • Get drag distance from d3.event.dx, d3.event.dy
  • Apply difference to group's transform attribute

When child dragged, group does not move as expected. It moves less than the dragged distance, and it begins to jump here and there.

What am I doing wrong here?

Edit:

Updated jsfiddle: http://jsfiddle.net/6NBy2/2/

I'm trying to drag the whole group by using one or more of it's children as dragging handles.

like image 471
altunyurt Avatar asked Jan 16 '14 13:01

altunyurt


1 Answers

This is an old question, but not really answered. I had exactly the same problem and wanted to drag the group by only one child (not all child elements of the <g>). The problem is, that the d3.event.dx/y is calculated relatively to the position of the <g>. And as soon as the <g> is moved by .attr(“transform”, “translate(x, y)”), the d3.event.dx/dy is adjusted to the new (smaller) value. This results in a jerky movement with approx. the half of the speed of the cursor. I found two possible solutions for this:

First (finally I ended up with this approach):

Append the drag handle rect directly to the svg and not to the <g>. So it is positioned relatively to the <svg> and not to the <g>. Then move both (the <rect> and the <g>) within the on drag function.

var svg = d3.select("svg");
var group = svg
    .append("g").attr("id", "group")
    .attr("transform", "translate(0, 0)");
group
    .append("rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", 100)
    .attr("height", 100)
    .style("fill", "green")
    .style("opacity", 0.4);
group
    .append("text")
    .attr("x", 10)
    .attr("y", 5)
    .attr("dominant-baseline", "hanging")
    .text("drag me");
handle = svg
    .append("rect")
        .data([{
        // Position of the rectangle
        x: 0,
        y: 0
    }]) 
    .attr("class", "draghandle")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", 100)
    .attr("height", 20)
    .style("fill", "blue")
    .style("opacity", 0.4)
    .attr("cursor", "move")
    .call(d3.drag().on("drag", function (d) {
            console.log("yep");
            d.x += d3.event.dx;
            d.y += d3.event.dy;
            
            // Move handle rect
            d3.select(this)
                .attr("x", function (d) {
                    return d.x;
                })
                .attr("y", function (d) {
                    return d.y;
                });
            
            // Move Group
            d3.select("#group").attr("transform", "translate(" + [d.x, d.y] + ")");
    }));
<body>
    <svg width="400" height="400"></svg>
    <script src="https://d3js.org/d3.v4.min.js"></script>
</body>

Second:

Check on which element the cursor was during the drag event with d3.event.sourceEvent.path[0] and run the drag function only if the handle <rect> was clicked. With this approach, all elements can be grouped within one <g> (no need for an additional <rect> outside the group). The downside of this method is, that the drag is also executed, if the cursor is moved over the drag handle with mouse down.

var svg = d3.select("svg");
var group = svg
    .append("g")
    .data([{
        // Position of the rectangle
        x: 0,
        y: 0
    }])
    .attr("id", "group")
    .attr("transform", function (d) {
            return "translate(" + d.x + ", " + d.y + ")"
    })
    .call(d3.drag().on("drag", function (d) {
        if (d3.event.sourceEvent.target.classList.value === "draghandle") {
            console.log("yep");
            d.x += d3.event.dx;
            d.y += d3.event.dy;
            d3.select(this).attr("transform", function (d) {
                return "translate(" + [d.x, d.y] + ")"
            })
        } else {
            console.log("nope");
            return;
        }
    }));
group
    .append("rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", 100)
    .attr("height", 100)
    .style("fill", "green")
    .style("opacity", 0.4);
group
    .append("text")
    .attr("x", 10)
    .attr("y", 5)
    .attr("dominant-baseline", "hanging")
    .text("drag me");
handle = group
    .append("rect")
    .attr("class", "draghandle")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", 100)
    .attr("height", 20)
    .style("fill", "blue")
    .style("opacity", 0.4)
    .attr("cursor", "move");
<body>
    <svg width="400" height="400"></svg>
    <script src="https://d3js.org/d3.v4.min.js"></script>
</body>
like image 114
roland Avatar answered Sep 20 '22 14:09

roland