1 /**
  2  * Returns a new zoom behavior to be registered on mousewheel events.
  3  *
  4  * @class Implements interactive zooming using mousewheel events. Register this
  5  * behavior on panels to allow zooming. This behavior can be used in tandem with
  6  * {@link pv.Behavior.pan} to allow both panning and zooming:
  7  *
  8  * <pre>    .event("mousedown", pv.Behavior.pan())
  9  *     .event("mousewheel", pv.Behavior.zoom())</pre>
 10  *
 11  * The zoom behavior currently supports only mousewheel events; support for
 12  * keyboard shortcuts and gesture events to improve accessibility may be added
 13  * in the future.
 14  *
 15  * <p>The implementation of this behavior relies on the panel's
 16  * <tt>transform</tt> property, which specifies a matrix transformation that is
 17  * applied to child marks. Note that the transform property only affects the
 18  * panel's children, but not the panel itself; therefore the panel's fill and
 19  * stroke will not change when the contents are zoomed. The built-in support for
 20  * transforms only supports uniform scaling and translates, which is sufficient
 21  * for panning and zooming.  Note that this is not a strict geometric
 22  * transformation, as the <tt>lineWidth</tt> property is scale-aware: strokes
 23  * are drawn at constant size independent of scale.
 24  *
 25  * <p>Panels have transparent fill styles by default; this means that panels may
 26  * not receive mousewheel events to zoom. To fix this problem, either given the
 27  * panel a visible fill style (such as "white"), or set the <tt>events</tt>
 28  * property to "all" such that the panel receives events despite its transparent
 29  * fill.
 30  *
 31  * <p>The zoom behavior has optional support for bounding. If enabled, the user
 32  * will not be able to zoom out farther than the initial bounds. This feature is
 33  * designed to work in conjunction with the pan behavior.
 34  *
 35  * @extends pv.Behavior
 36  * @see pv.Panel#transform
 37  * @see pv.Mark#scale
 38  * @param {number} speed
 39  */
 40 pv.Behavior.zoom = function(speed) {
 41   var bound; // whether to bound to the panel
 42 
 43   if (!arguments.length) speed = 1 / 48;
 44 
 45   /** @private */
 46   function mousewheel() {
 47     var v = this.mouse(),
 48         k = pv.event.wheel * speed,
 49         m = this.transform().translate(v.x, v.y)
 50             .scale((k < 0) ? (1e3 / (1e3 - k)) : ((1e3 + k) / 1e3))
 51             .translate(-v.x, -v.y);
 52     if (bound) {
 53       m.k = Math.max(1, m.k);
 54       m.x = Math.max((1 - m.k) * this.width(), Math.min(0, m.x));
 55       m.y = Math.max((1 - m.k) * this.height(), Math.min(0, m.y));
 56     }
 57     this.transform(m).render();
 58     pv.Mark.dispatch("zoom", this.scene, this.index);
 59   }
 60 
 61   /**
 62    * Sets or gets the bound parameter. If bounding is enabled, the user will not
 63    * be able to zoom out farther than the initial panel bounds. Bounding is not
 64    * enabled by default. If this behavior is used in tandem with the pan
 65    * behavior, both should use the same bound parameter.
 66    *
 67    * <p>Note: enabling bounding after zooming has already occurred will not
 68    * immediately reset the transform. Bounding should be enabled before the zoom
 69    * behavior is applied.
 70    *
 71    * @function
 72    * @returns {pv.Behavior.zoom} this, or the current bound parameter.
 73    * @name pv.Behavior.zoom.prototype.bound
 74    * @param {boolean} [x] the new bound parameter.
 75    */
 76   mousewheel.bound = function(x) {
 77     if (arguments.length) {
 78       bound = Boolean(x);
 79       return this;
 80     }
 81     return Boolean(bound);
 82   };
 83 
 84   return mousewheel;
 85 };
 86