用于触摸设备的D3树中的拖放功能

时间:2015-04-21 06:15:54

标签: jquery ajax d3.js

我创建了D3树。拖放功能适用于台式机和笔记本电脑。 D3中是否有拖放功能支持触摸设备?

D3参考站点 - http://bl.ocks.org/robschmuecker/7880033具有相同的触控设备版本,拖放功能无法使用此代码

var nodeEnter = node.enter().append("g").call(dragListener).attr(
            "class", "node").attr("transform", function(d) {
        return "translate(" + source.y0 + "," + source.x0 + ")";
    }).on("mouseenter", nodeMouseover).on("mouseleave", node_onMouseOut)
            .on('click', click).attr('id', function(d) {
                return d.nodeId;
            });

dragListener = d3.behavior.drag()
    .on("dragstart", function(d) {
        if (d == root) {
            return;
        }
        dragStarted = true;
        nodes = tree.nodes(d);
        d3.event.sourceEvent.stopPropagation();
        // it's important that we suppress the mouseover event on the node being dragged. Otherwise it will absorb the mouseover event and the underlying node will not detect it d3.select(this).attr('pointer-events', 'none');
    })
    .on("drag", function(d) {
        if (d == root) {
            return;
        }
        if (dragStarted) {
            domNode = this;
            initiateDrag(d, domNode);
        }

        // get coords of mouseEvent relative to svg container to allow for panning
        relCoords = d3.mouse($('svg').get(0));
        if (relCoords[0] < panBoundary) {
            panTimer = true;
            pan(this, 'left');
        } else if (relCoords[0] > ($('svg').width() - panBoundary)) {

            panTimer = true;
            pan(this, 'right');
        } else if (relCoords[1] < panBoundary) {
            panTimer = true;
            pan(this, 'up');
        } else if (relCoords[1] > ($('svg').height() - panBoundary)) {
            panTimer = true;
            pan(this, 'down');
        } else {
            try {
                clearTimeout(panTimer);
            } catch (e) {

            }
        }

        d.x0 += d3.event.dy;
        d.y0 += d3.event.dx;
        var node = d3.select(this);
        node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")");
        updateTempConnector();
    }).on("dragend", function(d) {
        if (d == root) {
            return;
        }
        domNode = this;
        if (selectedNode) {
            // now remove the element from the parent, and insert it into the new elements children
            var index = draggingNode.parent.children.indexOf(draggingNode);
            if (index > -1) {
                draggingNode.parent.children.splice(index, 1);
            }
            if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') {
                if (typeof selectedNode.children !== 'undefined') {
                    selectedNode.children.push(draggingNode);
                } else {
                    selectedNode._children.push(draggingNode);
                }
            } else {
                selectedNode.children = [];
                selectedNode.children.push(draggingNode);
            }
            // Make sure that the node being added to is expanded so user can see added node is correctly moved
            expand(selectedNode);
            sortTree();
            endDrag();
        } else {
            endDrag();
        }
    });

3 个答案:

答案 0 :(得分:2)

Drag行为支持点击和触摸,但在您给出的示例中,放置的位置由mousover事件给出:

// phantom node to give us mouseover in a radius around it
nodeEnter.append("circle")
    .attr('class', 'ghostCircle')
    .attr("r", 30)
    .attr("opacity", 0.2) // change this to zero to hide the target area
    .style("fill", "red")
    .attr('pointer-events', 'mouseover')
    .on("mouseover", function(node) {
        overCircle(node);
    })
    .on("mouseout", function(node) {
        outCircle(node);
    });

mouseover事件不会在触控设备上触发。 您可以做的是删除ghostCircles,并在事件&#34;中拖动&#34;,在updateTempConnector()之前手动执行计算。

我可以提出类似的建议:

var draggedNode = d;
var nodes = d3.selectAll("g.node")
.filter(function (d, i) {
    if ((d.id != draggedNode.id) && (!isChildOf(draggedNode, d))) {
        return d;
    }
    return 0;
});

var distances = [];

nodes.each(function (d, i) {
    var distanceX = draggedNode.x0 - d.x;
    var distanceY = draggedNode.y0 - d.y;                       
    var distance = Math.sqrt( Math.pow(distanceX, 2) + Math.pow(distanceY, 2) );
    distances.push({
        distance: distance,
        distanceV: distanceX,   // vertical, <0 when draggedNode is above
        distanceH: distanceY,   // horizontal, <0 when draggedNode is on the left
        d: d
    });
});

// sort by shorter distances
distances.sort(function (a, b) {
    return a.distance - b.distance;
});

selectedNode = distances[0].d;

这将选择(作为未来父节点)最接近被拖动节点的节点。

答案 1 :(得分:1)

对于http://bl.ocks.org/robschmuecker/7880033示例,我通过执行以下两项操作解决了这个问题:

首先:在你的&#34;拖动&#34;触发updateTempConnector之前的事件添加:

if ('ontouchstart' in window) {
    goalView.selectedNode = closestNode(d);
}

第二:然后定义以下函数:

var closestNode = function(d){
    var ghostCircleRadius = 30;
    var nodes = tree.nodes(goalView.rootNode).reverse();
    var minDistance = 100000000; //sufficiently large number
    var returnNode = null;
    for (var i = 0; i < nodes.length; i++) {
        var distance = (nodes[i].x0 - d.x0)*(nodes[i].x0 - d.x0) + (nodes[i].y0 - d.y0)*(nodes[i].y0 - d.y0);
        if (distance > 0){
            if (distance < minDistance && distance < ghostCircleRadius*ghostCircleRadius) {
                minDistance = distance;
                returnNode = nodes[i];
            }
        }
    }

    return returnNode;
};

这基本上会检查最近的节点,并检查我们的draggingNode是否在nearestNode的鬼圈半径范围内。

答案 2 :(得分:0)

D3 Drag Behavior自动创建事件侦听器以处理元素上的拖动手势。支持鼠标事件和触摸事件。

  

注意:确保您使用的是最新版本的D3