Commit 38724915 authored by Mike Bostock's avatar Mike Bostock
Browse files

Add d3.touch; fix #1786.

The drag behavior no longer crashes when the element being dragged is removed
from the DOM. In addition, the new d3.touch method extracts a single identified
touch from the current touch event, making it more efficient during multitouch.
The drag behavior now assigns touchmove and touchend listeners on the target
element of the touchstart event, rather than the window.
parent ee23f941
......@@ -1127,47 +1127,39 @@
}) : [];
};
d3.behavior.drag = function() {
var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, "mousemove", "mouseup"), touchstart = dragstart(touchid, touchposition, "touchmove", "touchend");
var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_behavior_dragMouseSubject, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_behavior_dragTouchSubject, "touchmove", "touchend");
function drag() {
this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
}
function touchid() {
return d3.event.changedTouches[0].identifier;
}
function touchposition(parent, id) {
return d3.touches(parent).filter(function(p) {
return p.identifier === id;
})[0];
}
function dragstart(id, position, move, end) {
function dragstart(id, position, subject, move, end) {
return function() {
var target = this, parent = target.parentNode, event_ = event.of(target, arguments), eventTarget = d3.event.target, eventId = id(), drag = eventId == null ? "drag" : "drag-" + eventId, origin_ = position(parent, eventId), dragged = 0, offset, w = d3.select(d3_window).on(move + "." + drag, moved).on(end + "." + drag, ended), dragRestore = d3_event_dragSuppress();
var element = this, parent = element.parentNode, dispatch = event.of(element, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragTarget = d3.event.target, dragSubject = d3.select(subject()).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(), position0 = position(parent, dragId);
if (origin) {
offset = origin.apply(target, arguments);
offset = [ offset.x - origin_[0], offset.y - origin_[1] ];
dragOffset = origin.apply(element, arguments);
dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
} else {
offset = [ 0, 0 ];
dragOffset = [ 0, 0 ];
}
event_({
dispatch({
type: "dragstart"
});
function moved() {
var p = position(parent, eventId), dx = p[0] - origin_[0], dy = p[1] - origin_[1];
var position1 = position(parent, dragId), dx = position1[0] - position0[0], dy = position1[1] - position0[1];
dragged |= dx | dy;
origin_ = p;
event_({
position0 = position1;
dispatch({
type: "drag",
x: p[0] + offset[0],
y: p[1] + offset[1],
x: position1[0] + dragOffset[0],
y: position1[1] + dragOffset[1],
dx: dx,
dy: dy
});
}
function ended() {
if (position(parent, eventId)) return;
w.on(move + "." + drag, null).on(end + "." + drag, null);
dragRestore(dragged && d3.event.target === eventTarget);
event_({
if (dragId != null && position(parent, dragId)) return;
dragSubject.on(move + dragName, null).on(end + dragName, null);
dragRestore(dragged && d3.event.target === dragTarget);
dispatch({
type: "dragend"
});
}
......@@ -1180,6 +1172,15 @@
};
return d3.rebind(drag, event, "on");
};
function d3_behavior_dragTouchId() {
return d3.event.changedTouches[0].identifier;
}
function d3_behavior_dragTouchSubject() {
return d3.event.target;
}
function d3_behavior_dragMouseSubject() {
return d3_window;
}
var π = Math.PI, τ = 2 * π, halfπ = π / 2, ε = 1e-6, ε2 = ε * ε, d3_radians = π / 180, d3_degrees = 180 / π;
function d3_sgn(x) {
return x > 0 ? 1 : x < 0 ? -1 : 0;
......@@ -2048,6 +2049,14 @@
};
d3.csv = d3.dsv(",", "text/csv");
d3.tsv = d3.dsv(" ", "text/tab-separated-values");
d3.touch = function(container, touches, identifier) {
if (arguments.length < 3) identifier = touches, touches = d3_eventSource().touches;
if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
if ((touch = touches[i]).identifier === identifier) {
return d3_mousePoint(container, touch);
}
}
};
var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_active, d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) {
setTimeout(callback, 17);
};
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -9,61 +9,59 @@ import "behavior";
d3.behavior.drag = function() {
var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"),
origin = null,
mousedown = dragstart(d3_noop, d3.mouse, "mousemove", "mouseup"),
touchstart = dragstart(touchid, touchposition, "touchmove", "touchend");
mousedown = dragstart(d3_noop, d3.mouse, d3_behavior_dragMouseSubject, "mousemove", "mouseup"),
touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_behavior_dragTouchSubject, "touchmove", "touchend");
function drag() {
this.on("mousedown.drag", mousedown)
.on("touchstart.drag", touchstart);
}
function touchid() {
return d3.event.changedTouches[0].identifier;
}
function touchposition(parent, id) {
return d3.touches(parent).filter(function(p) { return p.identifier === id; })[0];
}
function dragstart(id, position, move, end) {
function dragstart(id, position, subject, move, end) {
return function() {
var target = this,
parent = target.parentNode,
event_ = event.of(target, arguments),
eventTarget = d3.event.target,
eventId = id(),
drag = eventId == null ? "drag" : "drag-" + eventId,
origin_ = position(parent, eventId),
var element = this,
parent = element.parentNode,
dispatch = event.of(element, arguments),
dragged = 0,
offset,
w = d3.select(d3_window).on(move + "." + drag, moved).on(end + "." + drag, ended),
dragRestore = d3_event_dragSuppress();
dragId = id(),
dragName = ".drag" + (dragId == null ? "" : "-" + dragId),
dragOffset,
dragTarget = d3.event.target,
dragSubject = d3.select(subject()).on(move + dragName, moved).on(end + dragName, ended),
dragRestore = d3_event_dragSuppress(),
position0 = position(parent, dragId);
if (origin) {
offset = origin.apply(target, arguments);
offset = [offset.x - origin_[0], offset.y - origin_[1]];
dragOffset = origin.apply(element, arguments);
dragOffset = [dragOffset.x - position0[0], dragOffset.y - position0[1]];
} else {
offset = [0, 0];
dragOffset = [0, 0];
}
event_({type: "dragstart"});
dispatch({type: "dragstart"});
function moved() {
var p = position(parent, eventId),
dx = p[0] - origin_[0],
dy = p[1] - origin_[1];
var position1 = position(parent, dragId),
dx = position1[0] - position0[0],
dy = position1[1] - position0[1];
dragged |= dx | dy;
origin_ = p;
position0 = position1;
event_({type: "drag", x: p[0] + offset[0], y: p[1] + offset[1], dx: dx, dy: dy});
dispatch({
type: "drag",
x: position1[0] + dragOffset[0],
y: position1[1] + dragOffset[1],
dx: dx,
dy: dy
});
}
function ended() {
if (position(parent, eventId)) return; // this touch still active
w.on(move + "." + drag, null).on(end + "." + drag, null);
dragRestore(dragged && d3.event.target === eventTarget);
event_({type: "dragend"});
if (dragId != null && position(parent, dragId)) return; // this touch still active
dragSubject.on(move + dragName, null).on(end + dragName, null);
dragRestore(dragged && d3.event.target === dragTarget);
dispatch({type: "dragend"});
}
};
}
......@@ -76,3 +74,21 @@ d3.behavior.drag = function() {
return d3.rebind(drag, event, "on");
};
// While it is possible to receive a touchstart event with more than one changed
// touch, the event is only shared by touches on the same target; for new
// touches targetting different elements, multiple touchstart events are
// received even when the touches start simultaneously. Since multiple touches
// cannot move the same target to different locations concurrently without
// tearing the fabric of spacetime, we allow the first touch to win.
function d3_behavior_dragTouchId() {
return d3.event.changedTouches[0].identifier;
}
function d3_behavior_dragTouchSubject() {
return d3.event.target;
}
function d3_behavior_dragMouseSubject() {
return d3_window;
}
import "dispatch";
import "event";
import "mouse";
import "touch";
import "touches";
import "timer";
import "event";
import "mouse";
d3.touch = function(container, touches, identifier) {
if (arguments.length < 3) identifier = touches, touches = d3_eventSource().touches;
if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
if ((touch = touches[i]).identifier === identifier) {
return d3_mousePoint(container, touch);
}
}
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment