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