Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The mouse over event is not fired during the drag and drop action in d3.js

I'm trying to drag a circle over other circle to connect two path. The problem is when I drag a circle (c2) over another (c1), if the circle c1 is created before of the circle c2, the c1 mouse over event is not fired.

Here the js fiddle link

    var closedRoad = true;

    var width = 960,
        height = 500;

    var points = d3.range(1, 5).map(function(i) {
        return [i * width / 5, 50 + Math.random() * (height - 100)];
    });

    var points1 = d3.range(1, 5).map(function(i) {
        return [i * width / 5, 50 + Math.random() * (height - 100)];
    });

    var points2 = d3.range(1, 5).map(function(i) {
        return [i * width / 5, 50 + Math.random() * (height - 100)];
    });

    var count = 0;
    var ways =  [];

    var currentWay = null;

    var selected = null;

    var line = d3.svg.line();

    var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

    var rect = svg.append("rect")
    .attr("width", width)
    .attr("height", height);

    ways.forEach(function(way, i) {
        svg.append("path")
        .datum(way)
        .attr("id", "p" + way.id)
        .attr("class", "line")
        .call(redraw);
    });

    d3.select(window)
    .on("keydown", keydown)
    //.on("mousemove", mousemove)
    //.on("mouseup", mouseup)
    .on("mousedown", mousedown)
    .on("dblclick", dblclick);

    function redraw(way) {
        way.attr("d", function(d) { return line(d.pts); });

        var circle = svg.selectAll(".way" + way.data()[0].id)
        .data(way.data()[0].pts, function(d) { return d; });
        circle.enter().append("circle")
        .attr("class", "way" + way.data()[0].id)
        .attr("r", 1e-6)
        .on("mousedown", function(d) { 
            if (closedRoad) {
                currentWay = way.data()[0];
                selected = d; 

                if (d3.event) {
                    d3.event.preventDefault();
                    d3.event.stopPropagation();
                }
            }
        })
        .on("mouseover", function() { d3.select(d3.event.target).classed("highlight", true); })
        .on("mouseout", function() { d3.select(d3.event.target).classed("highlight", false); })
        .transition()
        .duration(750)
        .ease("elastic")
        .attr("r", 6.5);

        circle
        .attr("cx", function(d) { return d[0]; })
        .attr("cy", function(d) { return d[1]; });

        ////
        var drag = d3.behavior.drag();
        drag.on("dragstart", function(d) {
            console.log('START');
        })
        .on("drag", function(d) {
            console.log('MOVE');

            var m = d3.mouse(svg.node());
            d[0] = Math.max(0, Math.min(width, m[0]));
            d[1] = Math.max(0, Math.min(height, m[1]));

            //redraw(way);
            redrawAll();
        })
        .on("dragend", function(d) {
            console.log('END');
        });

        circle.call(drag);
        ////

        circle.exit().remove();

    }

    function dblclick() {
        currentWay.pts.pop();
        //redraw(svg.select("#p" + currentWay.id));
        redrawAll();

        closedRoad = true;
    }

    function mousedown() {
        if (closedRoad) {
            currentWay = { id: ++count, pts: [] };
            ways.push(currentWay); 

            svg.append("path")
            .datum(currentWay)
            .attr("id", "p" + currentWay.id)
            .attr("class", "line")
            .on("mouseover", function() { 
                d3.select(d3.event.target).classed("highlight", true); 
            })
            .on("mouseout", function() { d3.select(d3.event.target).classed("highlight", false); })
            .call(redraw);

            closedRoad = false;
        }

        currentWay.pts.push(selected = d3.mouse(svg.node()));
        //redraw(svg.select("#p" + currentWay.id));
        redrawAll();
    }

    function redrawAll() {
        ways.forEach(function(way, i) {
            redraw(svg.select("#p" + way.id));
        });
    }

    function mousemove() {

    }


    function keydown() {
        if (!selected) return;
        switch (d3.event.keyCode) {
            case 8: // backspace
            case 46: { // delete
                var i = currentWay.pts.indexOf(selected);
                currentWay.pts.splice(i, 1);
                selected = currentWay.pts.length ? currentWay.pts[i > 0 ? i - 1 : 0] : null;
                redraw(svg.select("#p" + currentWay.id));
                break;
            }
        }
    }

Can you help me?

Thanks! aGO!

like image 848
aGO Avatar asked Feb 14 '23 22:02

aGO


2 Answers

The problem is simply that the 'mouseover' event only gets triggered on the top-most element when two elements are painted one over top of each other. That is true regardless of whether or not you are handling the mouse event for the top-most element. Changing that behaviour is going to require considerable work-arounds, none of which are ideal.

Some possible solutions:

  • In your drag function, repeatedly check to see if there is another circle at this point.

    You can use the SVGSVGElement.getIntersectionList() method to find all the elements in a given rectangle. Note that this method is called on an SVG node, not on a d3 selection.

  • When you are dragging a node, make it "transparent" to mouse events by setting the style pointer-events:none; on it.

    Of course, this will also make it transparent to the drag event, so you will have to add the drag event to the container instead of the nodes, then figure out which circle is being dragged in the 'dragstart' event, change its pointer-events style, and store the circle selection in a variable accessible by your drag function (which will move it the same as your current code) and dragend function (which will re-set its pointer-events style). You'll also need to add a background rectangle to the <svg> or <g> element that is assigned the drag behaviour, so that it will respond to mouse events even when the mouse is only over a "transparent" circle.

  • When you are dragging a node, move it to the bottom of the painting order so all other nodes are painted over-top.

    Since SVG doesn't have a 'z-index' property like HTML does, the only way to make this happen is to actually re-order the DOM using selection.order() or plain Javascript insertBefore().

  • Or, accept that this is just a small aesthetic imperfection, and doesn't change the functionality of your code, so isn't worth the hassle and performance impacts of the above methods.

    If you don't like the fact that some circles change colour when dragged over, you could set a 'dragging' class on the svg for the duration of drag, and have your css for the 'highlight' class only make a visible change if it is not a child of 'svg.dragging'.

like image 51
AmeliaBR Avatar answered May 11 '23 23:05

AmeliaBR


I added a bit of code that picks up if the user is moving circles and if he is not to crate a new circle I hope this helps.

    var closedRoad = true;
    var width = 960,
        height = 500;

    var points = d3.range(1, 5).map(function(i) {
        return [i * width / 5, 50 + Math.random() * (height - 100)];
    });

    var points1 = d3.range(1, 5).map(function(i) {
        return [i * width / 5, 50 + Math.random() * (height - 100)];
    });

    var points2 = d3.range(1, 5).map(function(i) {
        return [i * width / 5, 50 + Math.random() * (height - 100)];
    });

    var count = 0;
    var ways =  [];

    var currentWay = null;

    var selected = null;

    var line = d3.svg.line();

    var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

    var rect = svg.append("rect")
    .attr("width", width)
    .attr("height", height);

    ways.forEach(function(way, i) {
        svg.append("path")
        .datum(way)
        .attr("id", "p" + way.id)
        .attr("class", "line")
        .call(redraw);
    });

    d3.select(window)
    .on("keydown", keydown)
    //.on("mousemove", mousemove)
    .on("mouseup", mouseup)
    .on("mousedown", mousedown)
    .on("dblclick", dblclick);

    function redraw(way) {
        way.attr("d", function(d) { return line(d.pts); });

        var circle = svg.selectAll(".way" + way.data()[0].id)
        .data(way.data()[0].pts, function(d) { return d; });
        circle.enter().append("circle")
        .attr("class", "way" + way.data()[0].id)
        .attr("r", 1e-6)
        .on("mousedown", function(d) { 
            if (closedRoad) {
                currentWay = way.data()[0];
                selected = d; 

                if (d3.event) {
                    d3.event.preventDefault();
                    d3.event.stopPropagation();
                }
            }
        })
        .on("mouseover", function() { d3.select(d3.event.target).classed("highlight", true); })
        .on("mouseout", function() { d3.select(d3.event.target).classed("highlight", false); })
        .transition()
        .duration(750)
        .ease("elastic")
        .attr("r", 6.5);

        circle
        .attr("cx", function(d) { return d[0]; })
        .attr("cy", function(d) { return d[1]; });

        ////
        var drag = d3.behavior.drag();
        drag.on("dragstart", function(d) {
            console.log('START');
        })
        .on("drag", function(d) {
            console.log('MOVE');

            var m = d3.mouse(svg.node());
            d[0] = Math.max(0, Math.min(width, m[0]));
            d[1] = Math.max(0, Math.min(height, m[1]));

            //redraw(way);
            redrawAll();
        })
        .on("dragend", function(d) {
            console.log('END');
        });

        circle.call(drag);
        ////

        circle.exit().remove();

        /*   if (d3.event) {
            d3.event.preventDefault();
            d3.event.stopPropagation();
        }*/
    }

    function dblclick() {
        currentWay.pts.pop();
        //redraw(svg.select("#p" + currentWay.id));
        redrawAll();

        closedRoad = true;
    }

    function mousedown() {
        start = event.pageX + event.pageY;
        console.log(start);

    }
    function mouseup(){
        if(start >= (event.pageX + event.pageY) - 10 && start <= (event.pageX + event.pageY) + 10){
        if (closedRoad) {
            currentWay = { id: ++count, pts: [] };
            ways.push(currentWay); 

            svg.append("path")
            .datum(currentWay)
            .attr("id", "p" + currentWay.id)
            .attr("class", "line")
            .on("mouseover", function() { 
                d3.select(d3.event.target).classed("highlight", true); 
            })
            .on("mouseout", function() { d3.select(d3.event.target).classed("highlight", false); })
            .call(redraw);

            closedRoad = false;
        }

        currentWay.pts.push(selected = d3.mouse(svg.node()));
        //redraw(svg.select("#p" + currentWay.id));
        redrawAll();
        }else{

        closedRoad = true;
        }
    }
    function redrawAll() {
        ways.forEach(function(way, i) {
            redraw(svg.select("#p" + way.id));
        });
    }

    function mousemove() {

    }


    function keydown() {
        if (!selected) return;
        switch (d3.event.keyCode) {
            case 8: // backspace
            case 46: { // delete
                var i = currentWay.pts.indexOf(selected);
                currentWay.pts.splice(i, 1);
                selected = currentWay.pts.length ? currentWay.pts[i > 0 ? i - 1 : 0] : null;
                redraw(svg.select("#p" + currentWay.id));
                break;
            }
        }
    }
like image 42
alastair Avatar answered May 11 '23 22:05

alastair