1 /** 2 * Returns a new drag behavior to be registered on mousedown events. 3 * 4 * @class Implements interactive dragging starting with mousedown events. 5 * Register this behavior on marks that should be draggable by the user, such as 6 * the selected region for brushing and linking. This behavior can be used in 7 * tandom with {@link pv.Behavior.select} to allow the selected region to be 8 * dragged interactively. 9 * 10 * <p>After the initial mousedown event is triggered, this behavior listens for 11 * mousemove and mouseup events on the window. This allows dragging to continue 12 * even if the mouse temporarily leaves the mark that is being dragged, or even 13 * the root panel. 14 * 15 * <p>This behavior requires that the data associated with the mark being 16 * dragged have <tt>x</tt> and <tt>y</tt> attributes that correspond to the 17 * mark's location in pixels. The mark's positional properties are not set 18 * directly by this behavior; instead, the positional properties should be 19 * defined as: 20 * 21 * <pre> .left(function(d) d.x) 22 * .top(function(d) d.y)</pre> 23 * 24 * Thus, the behavior does not move the mark directly, but instead updates the 25 * mark position by updating the underlying data. Note that if the positional 26 * properties are defined with bottom and right (rather than top and left), the 27 * drag behavior will be inverted, which will confuse users! 28 * 29 * <p>The drag behavior is bounded by the parent panel; the <tt>x</tt> and 30 * <tt>y</tt> attributes are clamped such that the mark being dragged does not 31 * extend outside the enclosing panel's bounds. To facilitate this, the drag 32 * behavior also queries for <tt>dx</tt> and <tt>dy</tt> attributes on the 33 * underlying data, to determine the dimensions of the bar being dragged. For 34 * non-rectangular marks, the drag behavior simply treats the mark as a point, 35 * which means that only the mark's center is bounded. 36 * 37 * <p>The mark being dragged is automatically re-rendered for each mouse event 38 * as part of the drag operation. In addition, a <tt>fix</tt> attribute is 39 * populated on the mark, which allows visual feedback for dragging. For 40 * example, to change the mark fill color while dragging: 41 * 42 * <pre> .fillStyle(function(d) d.fix ? "#ff7f0e" : "#aec7e8")</pre> 43 * 44 * In some cases, such as with network layouts, dragging the mark may cause 45 * related marks to change, in which case additional marks may also need to be 46 * rendered. This can be accomplished by listening for the drag 47 * psuedo-events:<ul> 48 * 49 * <li>dragstart (on mousedown) 50 * <li>drag (on mousemove) 51 * <li>dragend (on mouseup) 52 * 53 * </ul>For example, to render the parent panel while dragging, thus 54 * re-rendering all sibling marks: 55 * 56 * <pre> .event("mousedown", pv.Behavior.drag()) 57 * .event("drag", function() this.parent)</pre> 58 * 59 * This behavior may be enhanced in the future to allow more flexible 60 * configuration of drag behavior. 61 * 62 * @extends pv.Behavior 63 * @see pv.Behavior 64 * @see pv.Behavior.select 65 * @see pv.Layout.force 66 */ 67 pv.Behavior.drag = function() { 68 var scene, // scene context 69 index, // scene context 70 p, // particle being dragged 71 v1, // initial mouse-particle offset 72 max; 73 74 /** @private */ 75 function mousedown(d) { 76 index = this.index; 77 scene = this.scene; 78 var m = this.mouse(); 79 v1 = ((p = d).fix = pv.vector(d.x, d.y)).minus(m); 80 max = { 81 x: this.parent.width() - (d.dx || 0), 82 y: this.parent.height() - (d.dy || 0) 83 }; 84 scene.mark.context(scene, index, function() { this.render(); }); 85 pv.Mark.dispatch("dragstart", scene, index); 86 } 87 88 /** @private */ 89 function mousemove() { 90 if (!scene) return; 91 scene.mark.context(scene, index, function() { 92 var m = this.mouse(); 93 p.x = p.fix.x = Math.max(0, Math.min(v1.x + m.x, max.x)); 94 p.y = p.fix.y = Math.max(0, Math.min(v1.y + m.y, max.y)); 95 this.render(); 96 }); 97 pv.Mark.dispatch("drag", scene, index); 98 } 99 100 /** @private */ 101 function mouseup() { 102 if (!scene) return; 103 p.fix = null; 104 scene.mark.context(scene, index, function() { this.render(); }); 105 pv.Mark.dispatch("dragend", scene, index); 106 scene = null; 107 } 108 109 pv.listen(window, "mousemove", mousemove); 110 pv.listen(window, "mouseup", mouseup); 111 return mousedown; 112 }; 113