1 /** 2 * Returns a new pan behavior to be registered on mousedown events. 3 * 4 * @class Implements interactive panning starting with mousedown events. 5 * Register this behavior on panels to allow panning. This behavior can be used 6 * in tandem with {@link pv.Behavior.zoom} to allow both panning and zooming: 7 * 8 * <pre> .event("mousedown", pv.Behavior.pan()) 9 * .event("mousewheel", pv.Behavior.zoom())</pre> 10 * 11 * The pan behavior currently supports only mouse events; support for keyboard 12 * shortcuts to improve accessibility may be added in the future. 13 * 14 * <p>After the initial mousedown event is triggered, this behavior listens for 15 * mousemove and mouseup events on the window. This allows panning to continue 16 * even if the mouse temporarily leaves the panel that is being panned, or even 17 * the root panel. 18 * 19 * <p>The implementation of this behavior relies on the panel's 20 * <tt>transform</tt> property, which specifies a matrix transformation that is 21 * applied to child marks. Note that the transform property only affects the 22 * panel's children, but not the panel itself; therefore the panel's fill and 23 * stroke will not change when the contents are panned. 24 * 25 * <p>Panels have transparent fill styles by default; this means that panels may 26 * not receive the initial mousedown event to start panning. To fix this 27 * problem, either given the panel a visible fill style (such as "white"), or 28 * set the <tt>events</tt> property to "all" such that the panel receives events 29 * despite its transparent fill. 30 * 31 * <p>The pan behavior has optional support for bounding. If enabled, the user 32 * will not be able to pan the panel outside of the initial bounds. This feature 33 * is designed to work in conjunction with the zoom behavior; otherwise, 34 * bounding the panel effectively disables all panning. 35 * 36 * @extends pv.Behavior 37 * @see pv.Behavior.zoom 38 * @see pv.Panel#transform 39 */ 40 pv.Behavior.pan = function() { 41 var scene, // scene context 42 index, // scene context 43 m1, // transformation matrix at the start of panning 44 v1, // mouse location at the start of panning 45 k, // inverse scale 46 bound; // whether to bound to the panel 47 48 /** @private */ 49 function mousedown() { 50 index = this.index; 51 scene = this.scene; 52 v1 = pv.vector(pv.event.pageX, pv.event.pageY); 53 m1 = this.transform(); 54 k = 1 / (m1.k * this.scale); 55 if (bound) { 56 bound = { 57 x: (1 - m1.k) * this.width(), 58 y: (1 - m1.k) * this.height() 59 }; 60 } 61 } 62 63 /** @private */ 64 function mousemove() { 65 if (!scene) return; 66 scene.mark.context(scene, index, function() { 67 var x = (pv.event.pageX - v1.x) * k, 68 y = (pv.event.pageY - v1.y) * k, 69 m = m1.translate(x, y); 70 if (bound) { 71 m.x = Math.max(bound.x, Math.min(0, m.x)); 72 m.y = Math.max(bound.y, Math.min(0, m.y)); 73 } 74 this.transform(m).render(); 75 }); 76 pv.Mark.dispatch("pan", scene, index); 77 } 78 79 /** @private */ 80 function mouseup() { 81 scene = null; 82 } 83 84 /** 85 * Sets or gets the bound parameter. If bounding is enabled, the user will not 86 * be able to pan outside the initial panel bounds; this typically applies 87 * only when the pan behavior is used in tandem with the zoom behavior. 88 * Bounding is not enabled by default. 89 * 90 * <p>Note: enabling bounding after panning has already occurred will not 91 * immediately reset the transform. Bounding should be enabled before the 92 * panning behavior is applied. 93 * 94 * @function 95 * @returns {pv.Behavior.pan} this, or the current bound parameter. 96 * @name pv.Behavior.pan.prototype.bound 97 * @param {boolean} [x] the new bound parameter. 98 */ 99 mousedown.bound = function(x) { 100 if (arguments.length) { 101 bound = Boolean(x); 102 return this; 103 } 104 return Boolean(bound); 105 }; 106 107 pv.listen(window, "mousemove", mousemove); 108 pv.listen(window, "mouseup", mouseup); 109 return mousedown; 110 }; 111