426 lines
12 KiB
JavaScript
426 lines
12 KiB
JavaScript
/**
|
|
* SVGPan library 1.2.2
|
|
* ======================
|
|
*
|
|
* Given an unique existing element with a given id (or by default, the first
|
|
* g-element), including the library into any SVG adds the following
|
|
* capabilities:
|
|
*
|
|
* - Mouse panning
|
|
* - Mouse zooming (using the wheel)
|
|
* - Object dragging
|
|
*
|
|
* You can configure the behaviour of the pan/zoom/drag via setOptions().
|
|
*
|
|
* Known issues:
|
|
*
|
|
* - Zooming (while panning) on Safari has still some issues
|
|
*
|
|
* Releases:
|
|
*
|
|
* 1.2.2, Tue Aug 30 17:21:56 CEST 2011, Andrea Leofreddi
|
|
* - Fixed viewBox on root tag (#7)
|
|
* - Improved zoom speed (#2)
|
|
*
|
|
* 1.2.1, Mon Jul 4 00:33:18 CEST 2011, Andrea Leofreddi
|
|
* - Fixed a regression with mouse wheel (now working on Firefox 5)
|
|
* - Working with viewBox attribute (#4)
|
|
* - Added "use strict;" and fixed resulting warnings (#5)
|
|
* - Added configuration variables, dragging is disabled by default (#3)
|
|
*
|
|
* 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
|
|
* Fixed a bug with browser mouse handler interaction
|
|
*
|
|
* 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui
|
|
* Updated the zoom code to support the mouse wheel on Safari/Chrome
|
|
*
|
|
* 1.0, Andrea Leofreddi
|
|
* First release
|
|
*/
|
|
|
|
/**
|
|
* @license
|
|
* This code is licensed under the following BSD license:
|
|
* Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com>. All rights
|
|
* reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL Andrea Leofreddi OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* The views and conclusions contained in the software and documentation are
|
|
* those of the authors and should not be interpreted as representing official
|
|
* policies, either expressed or implied, of Andrea Leofreddi.
|
|
*
|
|
*/
|
|
|
|
goog.provide('svgpan.SvgPan');
|
|
|
|
goog.require('goog.Disposable');
|
|
goog.require('goog.events');
|
|
goog.require('goog.events.EventType');
|
|
goog.require('goog.events.MouseWheelHandler');
|
|
|
|
|
|
|
|
/**
|
|
* Instantiates an SvgPan object.
|
|
* @param {string=} opt_graphElementId The id of the graph element.
|
|
* @param {Element=} opt_root An optional document root.
|
|
* @constructor
|
|
* @extends {goog.Disposable}
|
|
*/
|
|
svgpan.SvgPan = function(opt_graphElementId, opt_root) {
|
|
svgpan.SvgPan.base(this, 'constructor');
|
|
|
|
/** @private {Element} */
|
|
this.root_ = opt_root || document.documentElement;
|
|
|
|
/** @private {?string} */
|
|
this.graphElementId_ = opt_graphElementId || null;
|
|
|
|
/** @private {boolean} */
|
|
this.cancelNextClick_ = false;
|
|
|
|
/** @private {boolean} */
|
|
this.enablePan_ = true;
|
|
|
|
/** @private {boolean} */
|
|
this.enableZoom_ = true;
|
|
|
|
/** @private {boolean} */
|
|
this.enableDrag_ = false;
|
|
|
|
/** @private {number} */
|
|
this.zoomScale_ = 0.4;
|
|
|
|
/** @private {svgpan.SvgPan.State} */
|
|
this.state_ = svgpan.SvgPan.State.NONE;
|
|
|
|
/** @private {Element} */
|
|
this.svgRoot_ = null;
|
|
|
|
/** @private {Element} */
|
|
this.stateTarget_ = null;
|
|
|
|
/** @private {SVGPoint} */
|
|
this.stateOrigin_ = null;
|
|
|
|
/** @private {SVGMatrix} */
|
|
this.stateTf_ = null;
|
|
|
|
/** @private {goog.events.MouseWheelHandler} */
|
|
this.mouseWheelHandler_ = null;
|
|
|
|
this.setupHandlers_();
|
|
};
|
|
goog.inherits(svgpan.SvgPan, goog.Disposable);
|
|
|
|
|
|
/** @override */
|
|
svgpan.SvgPan.prototype.disposeInternal = function() {
|
|
svgpan.SvgPan.base(this, 'disposeInternal');
|
|
goog.events.removeAll(this.root_);
|
|
this.mouseWheelHandler_.dispose();
|
|
};
|
|
|
|
|
|
/**
|
|
* @enum {string}
|
|
*/
|
|
svgpan.SvgPan.State = {
|
|
NONE: 'none',
|
|
PAN: 'pan',
|
|
DRAG: 'drag'
|
|
};
|
|
|
|
|
|
/**
|
|
* Enables/disables panning the entire SVG (default = true).
|
|
* @param {boolean} enabled Whether or not to allow panning.
|
|
*/
|
|
svgpan.SvgPan.prototype.setPanEnabled = function(enabled) {
|
|
this.enablePan_ = enabled;
|
|
};
|
|
|
|
|
|
/**
|
|
* Enables/disables zooming (default = true).
|
|
* @param {boolean} enabled Whether or not to allow zooming (default = true).
|
|
*/
|
|
svgpan.SvgPan.prototype.setZoomEnabled = function(enabled) {
|
|
this.enableZoom_ = enabled;
|
|
};
|
|
|
|
|
|
/**
|
|
* Enables/disables dragging individual SVG objects (default = false).
|
|
* @param {boolean} enabled Whether or not to allow dragging of objects.
|
|
*/
|
|
svgpan.SvgPan.prototype.setDragEnabled = function(enabled) {
|
|
this.enableDrag_ = enabled;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the sensitivity of mousewheel zooming (default = 0.4).
|
|
* @param {number} scale The new zoom scale.
|
|
*/
|
|
svgpan.SvgPan.prototype.setZoomScale = function(scale) {
|
|
this.zoomScale_ = scale;
|
|
};
|
|
|
|
|
|
/**
|
|
* Registers mouse event handlers.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.setupHandlers_ = function() {
|
|
goog.events.listen(this.root_, goog.events.EventType.CLICK,
|
|
goog.bind(this.handleMouseClick_, this));
|
|
goog.events.listen(this.root_, goog.events.EventType.MOUSEUP,
|
|
goog.bind(this.handleMouseUp_, this));
|
|
goog.events.listen(this.root_, goog.events.EventType.MOUSEDOWN,
|
|
goog.bind(this.handleMouseDown_, this));
|
|
goog.events.listen(this.root_, goog.events.EventType.MOUSEMOVE,
|
|
goog.bind(this.handleMouseMove_, this));
|
|
this.mouseWheelHandler_ = new goog.events.MouseWheelHandler(this.root_);
|
|
goog.events.listen(this.mouseWheelHandler_,
|
|
goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
|
|
goog.bind(this.handleMouseWheel_, this));
|
|
};
|
|
|
|
|
|
/**
|
|
* Retrieves the root element for SVG manipulation. The element is then cached.
|
|
* @param {Document} svgDoc The document.
|
|
* @return {Element} The svg root.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.getRoot_ = function(svgDoc) {
|
|
if (!this.svgRoot_) {
|
|
var r = this.graphElementId_ ?
|
|
svgDoc.getElementById(this.graphElementId_) : svgDoc.documentElement;
|
|
var t = r;
|
|
while (t != svgDoc) {
|
|
if (t.getAttribute('viewBox')) {
|
|
this.setCtm_(r, r.getCTM());
|
|
t.removeAttribute('viewBox');
|
|
}
|
|
t = t.parentNode;
|
|
}
|
|
this.svgRoot_ = r;
|
|
}
|
|
return this.svgRoot_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Instantiates an SVGPoint object with given event coordinates.
|
|
* @param {!goog.events.Event} evt The event with coordinates.
|
|
* @return {SVGPoint} The created point.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.getEventPoint_ = function(evt) {
|
|
return this.newPoint_(evt.clientX, evt.clientY);
|
|
};
|
|
|
|
|
|
/**
|
|
* Instantiates an SVGPoint object with given coordinates.
|
|
* @param {number} x The x coordinate.
|
|
* @param {number} y The y coordinate.
|
|
* @return {SVGPoint} The created point.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.newPoint_ = function(x, y) {
|
|
var p = this.root_.createSVGPoint();
|
|
p.x = x;
|
|
p.y = y;
|
|
return p;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the current transform matrix of an element.
|
|
* @param {Element} element The element.
|
|
* @param {SVGMatrix} matrix The transform matrix.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.setCtm_ = function(element, matrix) {
|
|
var s = 'matrix(' + matrix.a + ',' + matrix.b + ',' + matrix.c + ',' +
|
|
matrix.d + ',' + matrix.e + ',' + matrix.f + ')';
|
|
element.setAttribute('transform', s);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handle mouse wheel event.
|
|
* @param {!goog.events.Event} evt The event.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.handleMouseWheel_ = function(evt) {
|
|
if (!this.enableZoom_)
|
|
return;
|
|
|
|
// Prevents scrolling.
|
|
evt.preventDefault();
|
|
|
|
var svgDoc = evt.target.ownerDocument;
|
|
|
|
var delta = evt.deltaY / -9;
|
|
var z = Math.pow(1 + this.zoomScale_, delta);
|
|
var g = this.getRoot_(svgDoc);
|
|
var p = this.getEventPoint_(evt);
|
|
p = p.matrixTransform(g.getCTM().inverse());
|
|
|
|
// Compute new scale matrix in current mouse position
|
|
var k = this.root_.createSVGMatrix().translate(
|
|
p.x, p.y).scale(z).translate(-p.x, -p.y);
|
|
this.setCtm_(g, g.getCTM().multiply(k));
|
|
|
|
if (typeof(this.stateTf_) == 'undefined') {
|
|
this.stateTf_ = g.getCTM().inverse();
|
|
}
|
|
this.stateTf_ =
|
|
this.stateTf_ ? this.stateTf_.multiply(k.inverse()) : this.stateTf_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Handle mouse move event.
|
|
* @param {!goog.events.Event} evt The event.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.handleMouseMove_ = function(evt) {
|
|
if (evt.button != 0) {
|
|
return;
|
|
}
|
|
this.handleMove(evt.clientX, evt.clientY, evt.target.ownerDocument);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles mouse motion for the given coordinates.
|
|
* @param {number} x The x coordinate.
|
|
* @param {number} y The y coordinate.
|
|
* @param {Document} svgDoc The svg document.
|
|
*/
|
|
svgpan.SvgPan.prototype.handleMove = function(x, y, svgDoc) {
|
|
var g = this.getRoot_(svgDoc);
|
|
if (this.state_ == svgpan.SvgPan.State.PAN && this.enablePan_) {
|
|
// Pan mode
|
|
var p = this.newPoint_(x, y).matrixTransform(
|
|
/** @type {!SVGMatrix} */ (this.stateTf_));
|
|
this.setCtm_(g, this.stateTf_.inverse().translate(
|
|
p.x - this.stateOrigin_.x, p.y - this.stateOrigin_.y));
|
|
this.cancelNextClick_ = true;
|
|
} else if (this.state_ == svgpan.SvgPan.State.DRAG && this.enableDrag_) {
|
|
// Drag mode
|
|
var p = this.newPoint_(x, y).matrixTransform(g.getCTM().inverse());
|
|
this.setCtm_(this.stateTarget_, this.root_.createSVGMatrix().translate(
|
|
p.x - this.stateOrigin_.x, p.y - this.stateOrigin_.y).multiply(
|
|
g.getCTM().inverse()).multiply(this.stateTarget_.getCTM()));
|
|
this.stateOrigin_ = p;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handle click event.
|
|
* @param {!goog.events.Event} evt The event.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.handleMouseDown_ = function(evt) {
|
|
if (evt.button != 0) {
|
|
return;
|
|
}
|
|
// Prevent selection while dragging.
|
|
evt.preventDefault();
|
|
var svgDoc = evt.target.ownerDocument;
|
|
|
|
var g = this.getRoot_(svgDoc);
|
|
|
|
if (evt.target.tagName == 'svg' || !this.enableDrag_) {
|
|
// Pan mode
|
|
this.state_ = svgpan.SvgPan.State.PAN;
|
|
this.stateTf_ = g.getCTM().inverse();
|
|
this.stateOrigin_ = this.getEventPoint_(evt).matrixTransform(this.stateTf_);
|
|
} else {
|
|
// Drag mode
|
|
this.state_ = svgpan.SvgPan.State.DRAG;
|
|
this.stateTarget_ = /** @type {Element} */ (evt.target);
|
|
this.stateTf_ = g.getCTM().inverse();
|
|
this.stateOrigin_ = this.getEventPoint_(evt).matrixTransform(this.stateTf_);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handle mouse button release event.
|
|
* @param {!goog.events.Event} evt The event.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.handleMouseUp_ = function(evt) {
|
|
if (this.state_ != svgpan.SvgPan.State.NONE) {
|
|
this.endPanOrDrag();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Ends pan/drag mode.
|
|
*/
|
|
svgpan.SvgPan.prototype.endPanOrDrag = function() {
|
|
if (this.state_ != svgpan.SvgPan.State.NONE) {
|
|
this.state_ = svgpan.SvgPan.State.NONE;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handle mouse clicks.
|
|
* @param {!goog.events.Event} evt The event.
|
|
* @private
|
|
*/
|
|
svgpan.SvgPan.prototype.handleMouseClick_ = function(evt) {
|
|
// We only set cancelNextClick_ after panning occurred, and use it to prevent
|
|
// the default action that would otherwise take place when clicking on the
|
|
// element (for instance, navigation on clickable links, but also any click
|
|
// handler that may be set on an SVG element, in the case of active SVG
|
|
// content)
|
|
if (this.cancelNextClick_) {
|
|
// Cancel potential click handler on active SVG content.
|
|
evt.stopPropagation();
|
|
// Cancel navigation when panning on clickable links.
|
|
evt.preventDefault();
|
|
}
|
|
this.cancelNextClick_ = false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns the current state.
|
|
* @return {!svgpan.SvgPan.State}
|
|
*/
|
|
svgpan.SvgPan.prototype.getState = function() {
|
|
return this.state_;
|
|
};
|