1 /**
  2  * Returns a new resize behavior to be registered on mousedown events.
  3  *
  4  * @class Implements interactive resizing of a selection starting with mousedown
  5  * events. Register this behavior on selection handles that should be resizeable
  6  * by the user, such for brushing and linking. This behavior can be used in
  7  * tandom with {@link pv.Behavior.select} and {@link pv.Behavior.drag} to allow
  8  * the selected region to be selected and 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 resizing to continue
 12  * even if the mouse temporarily leaves the assigned panel, or even the root
 13  * panel.
 14  *
 15  * <p>This behavior requires that the data associated with the mark being
 16  * resized have <tt>x</tt>, <tt>y</tt>, <tt>dx</tt> and <tt>dy</tt> attributes
 17  * that correspond to the mark's location and dimensions in pixels. The mark's
 18  * positional properties are not set directly by this behavior; instead, the
 19  * positional properties should be defined as:
 20  *
 21  * <pre>    .left(function(d) d.x)
 22  *     .top(function(d) d.y)
 23  *     .width(function(d) d.dx)
 24  *     .height(function(d) d.dy)</pre>
 25  *
 26  * Thus, the behavior does not resize the mark directly, but instead updates the
 27  * size by updating the assigned panel's underlying data. Note that if the
 28  * positional properties are defined with bottom and right (rather than top and
 29  * left), the resize behavior will be inverted, which will confuse users!
 30  *
 31  * <p>The resize behavior is bounded by the assigned mark's enclosing panel; the
 32  * positional attributes are clamped such that the selection does not extend
 33  * outside the panel's bounds.
 34  *
 35  * <p>The mark being resized is automatically re-rendered for each mouse event
 36  * as part of the resize operation. This behavior may be enhanced in the future
 37  * to allow more flexible configuration. In some cases, such as with parallel
 38  * coordinates, resizing the selection may cause related marks to change, in
 39  * which case additional marks may also need to be rendered. This can be
 40  * accomplished by listening for the select psuedo-events:<ul>
 41  *
 42  * <li>resizestart (on mousedown)
 43  * <li>resize (on mousemove)
 44  * <li>resizeend (on mouseup)
 45  *
 46  * </ul>For example, to render the parent panel while resizing, thus
 47  * re-rendering all sibling marks:
 48  *
 49  * <pre>    .event("mousedown", pv.Behavior.resize("left"))
 50  *     .event("resize", function() this.parent)</pre>
 51  *
 52  * This behavior may be enhanced in the future to allow more flexible
 53  * configuration of the selection behavior.
 54  *
 55  * @extends pv.Behavior
 56  * @see pv.Behavior.select
 57  * @see pv.Behavior.drag
 58  */
 59 pv.Behavior.resize = function(side) {
 60   var scene, // scene context
 61       index, // scene context
 62       r, // region being selected
 63       m1; // initial mouse position
 64 
 65   /** @private */
 66   function mousedown(d) {
 67     index = this.index;
 68     scene = this.scene;
 69     m1 = this.mouse();
 70     r = d;
 71     switch (side) {
 72       case "left": m1.x = r.x + r.dx; break;
 73       case "right": m1.x = r.x; break;
 74       case "top": m1.y = r.y + r.dy; break;
 75       case "bottom": m1.y = r.y; break;
 76     }
 77     pv.Mark.dispatch("resizestart", scene, index);
 78   }
 79 
 80   /** @private */
 81   function mousemove() {
 82     if (!scene) return;
 83     scene.mark.context(scene, index, function() {
 84         var m2 = this.mouse();
 85         r.x = Math.max(0, Math.min(m1.x, m2.x));
 86         r.y = Math.max(0, Math.min(m1.y, m2.y));
 87         r.dx = Math.min(this.parent.width(), Math.max(m2.x, m1.x)) - r.x;
 88         r.dy = Math.min(this.parent.height(), Math.max(m2.y, m1.y)) - r.y;
 89         this.render();
 90       });
 91     pv.Mark.dispatch("resize", scene, index);
 92   }
 93 
 94   /** @private */
 95   function mouseup() {
 96     if (!scene) return;
 97     pv.Mark.dispatch("resizeend", scene, index);
 98     scene = null;
 99   }
100 
101   pv.listen(window, "mousemove", mousemove);
102   pv.listen(window, "mouseup", mouseup);
103   return mousedown;
104 };
105