Skip to content

Commit 18dcad7

Browse files
committed
Add drag.origin accessor.
This allows you to specify how the drag behavior should infer the current position of the targetted element, such that a fixed offset can be maintained between the mouse position and the element position for the duration of the drag gesture. For example, if your data is an {x, y} object, use origin(Object).
1 parent 3de298b commit 18dcad7

7 files changed

Lines changed: 139 additions & 47 deletions

File tree

d3.js

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4265,8 +4265,11 @@ var d3_svg_brushCursor = {
42654265
sw: "nesw-resize"
42664266
};
42674267
d3.behavior = {};
4268+
// TODO Track touch points by identifier.
4269+
42684270
d3.behavior.drag = function() {
4269-
var event = d3.dispatch("drag", "dragstart", "dragend");
4271+
var event = d3.dispatch("drag", "dragstart", "dragend"),
4272+
origin = null;
42704273

42714274
function drag() {
42724275
this
@@ -4284,10 +4287,16 @@ d3.behavior.drag = function() {
42844287
// snapshot the local context for subsequent dispatch
42854288
function start() {
42864289
d3_behavior_dragEvent = event;
4287-
d3_behavior_dragEventTarget = d3.event.target;
4288-
d3_behavior_dragOffset = d3_behavior_dragPoint((d3_behavior_dragTarget = this).parentNode);
4289-
d3_behavior_dragMoved = 0;
4290+
d3_behavior_dragTarget = this;
42904291
d3_behavior_dragArguments = arguments;
4292+
d3_behavior_dragOrigin = d3_behavior_dragPoint();
4293+
if (origin) {
4294+
d3_behavior_dragOffset = origin.apply(d3_behavior_dragTarget, d3_behavior_dragArguments);
4295+
d3_behavior_dragOffset = [d3_behavior_dragOffset.x - d3_behavior_dragOrigin[0], d3_behavior_dragOffset.y - d3_behavior_dragOrigin[1]];
4296+
} else {
4297+
d3_behavior_dragOffset = [0, 0];
4298+
}
4299+
d3_behavior_dragMoved = 0;
42914300
}
42924301

42934302
function mousedown() {
@@ -4300,30 +4309,38 @@ d3.behavior.drag = function() {
43004309
return drag;
43014310
};
43024311

4312+
drag.origin = function(x) {
4313+
if (!arguments.length) return origin;
4314+
origin = x;
4315+
return drag;
4316+
};
4317+
43034318
return drag;
43044319
};
43054320

43064321
var d3_behavior_dragEvent,
4307-
d3_behavior_dragEventTarget,
43084322
d3_behavior_dragTarget,
43094323
d3_behavior_dragArguments,
43104324
d3_behavior_dragOffset,
4325+
d3_behavior_dragOrigin,
43114326
d3_behavior_dragMoved,
43124327
d3_behavior_dragStopClick;
43134328

43144329
function d3_behavior_dragDispatch(type) {
4315-
var o = d3.event, p = d3_behavior_dragTarget.parentNode, dx = 0, dy = 0;
4330+
var p = d3_behavior_dragPoint(),
4331+
o = d3.event,
4332+
e = d3.event = {type: type};
43164333

43174334
if (p) {
4318-
p = d3_behavior_dragPoint(p);
4319-
dx = p[0] - d3_behavior_dragOffset[0];
4320-
dy = p[1] - d3_behavior_dragOffset[1];
4321-
d3_behavior_dragOffset = p;
4322-
d3_behavior_dragMoved |= dx | dy;
4335+
e.x = p[0] + d3_behavior_dragOffset[0];
4336+
e.y = p[1] + d3_behavior_dragOffset[1];
4337+
e.dx = p[0] - d3_behavior_dragOrigin[0];
4338+
e.dy = p[1] - d3_behavior_dragOrigin[1];
4339+
d3_behavior_dragMoved |= e.dx | e.dy;
4340+
d3_behavior_dragOrigin = p;
43234341
}
43244342

43254343
try {
4326-
d3.event = {dx: dx, dy: dy};
43274344
d3_behavior_dragEvent[type].apply(d3_behavior_dragTarget, d3_behavior_dragArguments);
43284345
} finally {
43294346
d3.event = o;
@@ -4332,10 +4349,12 @@ function d3_behavior_dragDispatch(type) {
43324349
o.preventDefault();
43334350
}
43344351

4335-
function d3_behavior_dragPoint(container, type) {
4336-
// TODO Track touch points by identifier.
4337-
var t = d3.event.changedTouches;
4338-
return t ? d3.svg.touches(container, t)[0] : d3.svg.mouse(container);
4352+
function d3_behavior_dragPoint() {
4353+
var p = d3_behavior_dragTarget.parentNode,
4354+
t = d3.event.changedTouches;
4355+
return p && (t
4356+
? d3.svg.touches(p, t)[0]
4357+
: d3.svg.mouse(p));
43394358
}
43404359

43414360
function d3_behavior_dragMove() {
@@ -4352,21 +4371,25 @@ function d3_behavior_dragMove() {
43524371
function d3_behavior_dragUp() {
43534372
if (!d3_behavior_dragTarget) return;
43544373
d3_behavior_dragDispatch("dragend");
4355-
d3_behavior_dragTarget = null;
4374+
4375+
d3_behavior_dragEvent =
4376+
d3_behavior_dragTarget =
4377+
d3_behavior_dragArguments =
4378+
d3_behavior_dragOffset =
4379+
d3_behavior_dragOrigin = null;
43564380

43574381
// If the node was moved, prevent the mouseup from propagating.
43584382
// Also prevent the subsequent click from propagating (e.g., for anchors).
4359-
if (d3_behavior_dragMoved && d3_behavior_dragEventTarget === d3.event.target) {
4383+
if (d3_behavior_dragMoved) {
43604384
d3_behavior_dragStopClick = true;
43614385
d3_eventCancel();
43624386
}
43634387
}
43644388

43654389
function d3_behavior_dragClick() {
4366-
if (d3_behavior_dragStopClick && d3_behavior_dragEventTarget === d3.event.target) {
4390+
if (d3_behavior_dragStopClick) {
43674391
d3_eventCancel();
43684392
d3_behavior_dragStopClick = false;
4369-
d3_behavior_dragEventTarget = null;
43704393
}
43714394
}
43724395
// TODO unbind zoom behavior?

d3.layout.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ d3.layout.force = function() {
475475
// use `node.call(force.drag)` to make nodes draggable
476476
force.drag = function() {
477477
if (!drag) drag = d3.behavior.drag()
478+
.origin(Object)
478479
.on("dragstart", dragstart)
479480
.on("drag", d3_layout_forceDrag)
480481
.on("dragend", d3_layout_forceDragEnd);
@@ -510,8 +511,8 @@ function d3_layout_forceDragEnd() {
510511
}
511512

512513
function d3_layout_forceDrag() {
513-
d3_layout_forceDragNode.px += d3.event.dx;
514-
d3_layout_forceDragNode.py += d3.event.dy;
514+
d3_layout_forceDragNode.px = d3.event.x;
515+
d3_layout_forceDragNode.py = d3.event.y;
515516
d3_layout_forceDragForce.resume(); // restart annealing
516517
}
517518

d3.layout.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

d3.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/drag/drag.html

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
5+
<style type="text/css">
6+
7+
svg {
8+
border: solid 1px #aaa;
9+
}
10+
11+
</style>
12+
</head>
13+
<body>
14+
<script type="text/javascript" src="../../d3.js"></script>
15+
<script type="text/javascript">
16+
17+
var w = 960,
18+
h = 500,
19+
r = 120;
20+
21+
var drag = d3.behavior.drag()
22+
.origin(Object)
23+
.on("drag", dragmove);
24+
25+
var svg = d3.select("body").append("svg:svg")
26+
.attr("width", w)
27+
.attr("height", h)
28+
29+
var circle = svg.append("svg:circle")
30+
.data([{x: w / 2, y: h / 2}])
31+
.attr("r", r)
32+
.attr("cx", function(d) { return d.x; })
33+
.attr("cy", function(d) { return d.y; })
34+
.call(drag);
35+
36+
function dragmove(d) {
37+
circle
38+
.attr("cx", d.x = Math.max(r, Math.min(w - r, d3.event.x)))
39+
.attr("cy", d.y = Math.max(r, Math.min(h - r, d3.event.y)));
40+
}
41+
42+
</script>
43+
</body>
44+
</html>

src/behavior/drag.js

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
// TODO Track touch points by identifier.
2+
13
d3.behavior.drag = function() {
2-
var event = d3.dispatch("drag", "dragstart", "dragend");
4+
var event = d3.dispatch("drag", "dragstart", "dragend"),
5+
origin = null;
36

47
function drag() {
58
this
@@ -17,10 +20,16 @@ d3.behavior.drag = function() {
1720
// snapshot the local context for subsequent dispatch
1821
function start() {
1922
d3_behavior_dragEvent = event;
20-
d3_behavior_dragEventTarget = d3.event.target;
21-
d3_behavior_dragOffset = d3_behavior_dragPoint((d3_behavior_dragTarget = this).parentNode);
22-
d3_behavior_dragMoved = 0;
23+
d3_behavior_dragTarget = this;
2324
d3_behavior_dragArguments = arguments;
25+
d3_behavior_dragOrigin = d3_behavior_dragPoint();
26+
if (origin) {
27+
d3_behavior_dragOffset = origin.apply(d3_behavior_dragTarget, d3_behavior_dragArguments);
28+
d3_behavior_dragOffset = [d3_behavior_dragOffset.x - d3_behavior_dragOrigin[0], d3_behavior_dragOffset.y - d3_behavior_dragOrigin[1]];
29+
} else {
30+
d3_behavior_dragOffset = [0, 0];
31+
}
32+
d3_behavior_dragMoved = 0;
2433
}
2534

2635
function mousedown() {
@@ -33,30 +42,38 @@ d3.behavior.drag = function() {
3342
return drag;
3443
};
3544

45+
drag.origin = function(x) {
46+
if (!arguments.length) return origin;
47+
origin = x;
48+
return drag;
49+
};
50+
3651
return drag;
3752
};
3853

3954
var d3_behavior_dragEvent,
40-
d3_behavior_dragEventTarget,
4155
d3_behavior_dragTarget,
4256
d3_behavior_dragArguments,
4357
d3_behavior_dragOffset,
58+
d3_behavior_dragOrigin,
4459
d3_behavior_dragMoved,
4560
d3_behavior_dragStopClick;
4661

4762
function d3_behavior_dragDispatch(type) {
48-
var o = d3.event, p = d3_behavior_dragTarget.parentNode, dx = 0, dy = 0;
63+
var p = d3_behavior_dragPoint(),
64+
o = d3.event,
65+
e = d3.event = {type: type};
4966

5067
if (p) {
51-
p = d3_behavior_dragPoint(p);
52-
dx = p[0] - d3_behavior_dragOffset[0];
53-
dy = p[1] - d3_behavior_dragOffset[1];
54-
d3_behavior_dragOffset = p;
55-
d3_behavior_dragMoved |= dx | dy;
68+
e.x = p[0] + d3_behavior_dragOffset[0];
69+
e.y = p[1] + d3_behavior_dragOffset[1];
70+
e.dx = p[0] - d3_behavior_dragOrigin[0];
71+
e.dy = p[1] - d3_behavior_dragOrigin[1];
72+
d3_behavior_dragMoved |= e.dx | e.dy;
73+
d3_behavior_dragOrigin = p;
5674
}
5775

5876
try {
59-
d3.event = {dx: dx, dy: dy};
6077
d3_behavior_dragEvent[type].apply(d3_behavior_dragTarget, d3_behavior_dragArguments);
6178
} finally {
6279
d3.event = o;
@@ -65,10 +82,12 @@ function d3_behavior_dragDispatch(type) {
6582
o.preventDefault();
6683
}
6784

68-
function d3_behavior_dragPoint(container, type) {
69-
// TODO Track touch points by identifier.
70-
var t = d3.event.changedTouches;
71-
return t ? d3.svg.touches(container, t)[0] : d3.svg.mouse(container);
85+
function d3_behavior_dragPoint() {
86+
var p = d3_behavior_dragTarget.parentNode,
87+
t = d3.event.changedTouches;
88+
return p && (t
89+
? d3.svg.touches(p, t)[0]
90+
: d3.svg.mouse(p));
7291
}
7392

7493
function d3_behavior_dragMove() {
@@ -85,20 +104,24 @@ function d3_behavior_dragMove() {
85104
function d3_behavior_dragUp() {
86105
if (!d3_behavior_dragTarget) return;
87106
d3_behavior_dragDispatch("dragend");
88-
d3_behavior_dragTarget = null;
107+
108+
d3_behavior_dragEvent =
109+
d3_behavior_dragTarget =
110+
d3_behavior_dragArguments =
111+
d3_behavior_dragOffset =
112+
d3_behavior_dragOrigin = null;
89113

90114
// If the node was moved, prevent the mouseup from propagating.
91115
// Also prevent the subsequent click from propagating (e.g., for anchors).
92-
if (d3_behavior_dragMoved && d3_behavior_dragEventTarget === d3.event.target) {
116+
if (d3_behavior_dragMoved) {
93117
d3_behavior_dragStopClick = true;
94118
d3_eventCancel();
95119
}
96120
}
97121

98122
function d3_behavior_dragClick() {
99-
if (d3_behavior_dragStopClick && d3_behavior_dragEventTarget === d3.event.target) {
123+
if (d3_behavior_dragStopClick) {
100124
d3_eventCancel();
101125
d3_behavior_dragStopClick = false;
102-
d3_behavior_dragEventTarget = null;
103126
}
104127
}

src/layout/force.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ d3.layout.force = function() {
264264
// use `node.call(force.drag)` to make nodes draggable
265265
force.drag = function() {
266266
if (!drag) drag = d3.behavior.drag()
267+
.origin(Object)
267268
.on("dragstart", dragstart)
268269
.on("drag", d3_layout_forceDrag)
269270
.on("dragend", d3_layout_forceDragEnd);
@@ -299,8 +300,8 @@ function d3_layout_forceDragEnd() {
299300
}
300301

301302
function d3_layout_forceDrag() {
302-
d3_layout_forceDragNode.px += d3.event.dx;
303-
d3_layout_forceDragNode.py += d3.event.dy;
303+
d3_layout_forceDragNode.px = d3.event.x;
304+
d3_layout_forceDragNode.py = d3.event.y;
304305
d3_layout_forceDragForce.resume(); // restart annealing
305306
}
306307

0 commit comments

Comments
 (0)