(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this.options.maxZoom * this.initialCTM.a) {setZoom.a = setZoom.d = wasZoom.a} if (setZoom.a !== wasZoom.a) { SvgUtils.setCTM(this.viewport, setZoom) // Cache zoom level this._zoom = setZoom.a } if (!this.stateTf) { this.stateTf = setZoom.inverse() } this.stateTf = this.stateTf.multiply(k.inverse()) if (this.options.onZoom) { this.options.onZoom(setZoom.a) } } SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) { // If not a SVGPoint but has x and y than create new point if (Utils.getType(point) !== 'SVGPoint' && 'x' in point && 'y' in point) { var _point = this.svg.createSVGPoint() _point.x = point.x _point.y = point.y point = _point } else { throw new Error('Given point is invalid') return } this.zoomAtPoint(this.svg, point, scale, absolute) } /** * Get zoom scale/level * * @return {float} zoom scale */ SvgPanZoom.prototype.getZoom = function() { return this._zoom } SvgPanZoom.prototype.resetZoom = function() { this.getPublicInstance().zoom(this.initialCTM.a) this.getPublicInstance().pan({x: this.initialCTM.e, y: this.initialCTM.f}) // Cache zoom level this._zoom = this.initialCTM.a // Cache pan level this._pan.x = this.initialCTM.e this._pan.y = this.initialCTM.f } /** * Handle mouse move event * * @param {object} evt Event */ SvgPanZoom.prototype.handleMouseMove = function(evt) { if (evt.preventDefault) { evt.preventDefault() } else { evt.returnValue = false } var svg = (evt.target.tagName === 'svg' || evt.target.tagName === 'SVG') ? evt.target : evt.target.ownerSVGElement || evt.target.correspondingElement.ownerSVGElement var point; if (this.state === 'pan' && this.options.panEnabled) { // Trigger beforePan if (Utils.isFunction(this.options.beforePan)) { this.options.beforePan() } // Pan mode point = SvgUtils.getEventPoint(evt).matrixTransform(this.stateTf) var viewportCTM = this.stateTf.inverse().translate(point.x - this.stateOrigin.x, point.y - this.stateOrigin.y) SvgUtils.setCTM(this.viewport, viewportCTM) // Cache pan level this._pan.x = viewportCTM.e this._pan.y = viewportCTM.f // Trigger onPan this.options.onPan(this._pan.x, this._pan.y) } else if (this.state === 'drag' && this.options.dragEnabled) { // Drag mode point = SvgUtils.getEventPoint(evt).matrixTransform(this.viewport.getCTM().inverse()) SvgUtils.setCTM(this.stateTarget, svg.createSVGMatrix().translate(point.x - this.stateOrigin.x, point.y - this.stateOrigin.y).multiply(this.viewport.getCTM().inverse()).multiply(this.stateTarget.getCTM())) this.stateOrigin = point; } } /** * Handle double click event * See handleMouseDown() for alternate detection method * * @param {object} evt Event */ SvgPanZoom.prototype.handleDblClick = function(evt) { var target = evt.target , svg = (target.tagName === 'svg' || target.tagName === 'SVG') ? target : target.ownerSVGElement || target.correspondingElement.ownerSVGElement if (evt.preventDefault) { evt.preventDefault() } else { evt.returnValue = false } // Check if target was a control button if (this.options.controlIconsEnabled) { var targetClass = target.getAttribute('class') || '' if (targetClass.indexOf('svg-pan-zoom-control') > -1) { return false } } var zoomFactor if (evt.shiftKey) { zoomFactor = 1/((1 + this.options.zoomScaleSensitivity) * 2) // zoom out when shift key pressed } else { zoomFactor = (1 + this.options.zoomScaleSensitivity) * 2 } var point = SvgUtils.getRelativeMousePoint(svg, evt) this.zoomAtPoint(svg, point, zoomFactor) } /** * Handle click event * * @param {object} evt Event */ SvgPanZoom.prototype.handleMouseDown = function(evt) { // Double click detection; more consistent than ondblclick if (evt.detail === 2){ this.handleDblClick(evt) } if (evt.preventDefault) { evt.preventDefault() } else { evt.returnValue = false } var svg = (evt.target.tagName === 'svg' || evt.target.tagName === 'SVG') ? evt.target : evt.target.ownerSVGElement || evt.target.correspondingElement.ownerSVGElement if (evt.target.tagName === 'svg' || !this.options.dragEnabled) { // Pan anyway when drag is disabled and the user clicked on an element // Pan mode this.state = 'pan' this.stateTf = this.viewport.getCTM().inverse() this.stateOrigin = SvgUtils.getEventPoint(evt).matrixTransform(this.stateTf) } else { // Drag mode this.state = 'drag' this.stateTarget = evt.target this.stateTf = this.viewport.getCTM().inverse() this.stateOrigin = SvgUtils.getEventPoint(evt).matrixTransform(this.stateTf) } } /** * Handle mouse button release event * * @param {object} evt Event */ SvgPanZoom.prototype.handleMouseUp = function(evt) { if (evt.preventDefault) { evt.preventDefault() } else { evt.returnValue = false } var svg = (evt.target.tagName === 'svg' || evt.target.tagName === 'SVG') ? evt.target : evt.target.ownerSVGElement || evt.target.correspondingElement.ownerSVGElement if (this.state === 'pan' || this.state === 'drag') { // Quit pan mode this.state = 'none' } } /** * Adjust viewport size (only) so it will fit in SVG * Does not center image * * @param {bool} dropCache drop viewBox cache and recalculate SVG's viewport sizes. Default false */ SvgPanZoom.prototype.fit = function(dropCache) { if (dropCache) { this.recacheViewBox() } var newScale = Math.min(this.width/(this._viewBox.width - this._viewBox.x), this.height/(this._viewBox.height - this._viewBox.y)) this.getPublicInstance().zoom(newScale) } /** * Adjust viewport pan (only) so it will be centered in SVG * Does not zoom/fit image * * @param {bool} dropCache drop viewBox cache and recalculate SVG's viewport sizes. Default false */ SvgPanZoom.prototype.center = function(dropCache) { if (dropCache) { this.recacheViewBox() } var offsetX = (this.width - (this._viewBox.width + this._viewBox.x) * this.getZoom()) * 0.5 , offsetY = (this.height - (this._viewBox.height + this._viewBox.y) * this.getZoom()) * 0.5 this.getPublicInstance().pan({x: offsetX, y: offsetY}) } /** * Pan to a rendered position * * @param {object} point {x: 0, y: 0} */ SvgPanZoom.prototype.pan = function(point) { // Trigger beforePan if (Utils.isFunction(this.options.beforePan)) { this.options.beforePan() } var viewportCTM = this.viewport.getCTM() viewportCTM.e = point.x viewportCTM.f = point.y SvgUtils.setCTM(this.viewport, viewportCTM) // Cache pan level this._pan.x = viewportCTM.e this._pan.y = viewportCTM.f // Trigger onPan this.options.onPan(this._pan.x, this._pan.y) } /** * Relatively pan the graph by a specified rendered position vector * * @param {object} point {x: 0, y: 0} */ SvgPanZoom.prototype.panBy = function(point) { // Trigger beforePan if (Utils.isFunction(this.options.beforePan)) { this.options.beforePan() } var viewportCTM = this.viewport.getCTM() viewportCTM.e += point.x viewportCTM.f += point.y SvgUtils.setCTM(this.viewport, viewportCTM) // Cache pan level this._pan.x = viewportCTM.e this._pan.y = viewportCTM.f // Trigger onPan this.options.onPan(this._pan.x, this._pan.y) } /** * Get pan vector * * @return {object} {x: 0, y: 0} */ SvgPanZoom.prototype.getPan = function() { // Do not return object directly because it will be possible to modify it using the reference return {x: this._pan.x, y: this._pan.y} } /** * Returns a public instance object * @return {object} Public instance object */ SvgPanZoom.prototype.getPublicInstance = function() { var that = this // Create cache if (!this.publicInstance) { this.publicInstance = { // Pan enablePan: function() {that.options.panEnabled = true} , disablePan: function() {that.options.panEnabled = false} , isPanEnabled: function() {return !!that.options.panEnabled} , pan: function(point) {that.pan(point)} , panBy: function(point) {that.panBy(point)} , getPan: function() {return that.getPan()} // Pan event , setBeforePan: function(fn) {that.options.beforePan = Utils.proxy(fn, that.publicInstance)} , setOnPan: function(fn) {that.options.onPan = Utils.proxy(fn, that.publicInstance)} // Drag , enableDrag: function() {that.options.dragEnabled = true} , disableDrag: function() {that.options.dragEnabled = false} , isDragEnabled: function() {return !!that.options.dragEnabled} // Zoom and Control Icons , enableZoom: function() { if (that.options.controlIconsEnabled && !that.options.zoomEnabled) { ControlIcons.enable(that) } that.options.zoomEnabled = true; } , disableZoom: function() { if (that.options.controlIconsEnabled && that.options.zoomEnabled) { ControlIcons.disable(that) } that.options.zoomEnabled = false; } , isZoomEnabled: function() {return !!that.options.zoomEnabled} , enableControlIcons: function() { if (that.options.zoomEnabled && !that.options.controlIconsEnabled) { that.options.controlIconsEnabled = true ControlIcons.enable(that) } } , disableControlIcons: function() { if (that.options.controlIconsEnabled) { that.options.controlIconsEnabled = false; ControlIcons.disable(that) } } , isControlIconsEnabled: function() {return !!that.options.controlIconsEnabled} // Zoom scale and bounds , setZoomScaleSensitivity: function(scale) {that.options.zoomScaleSensitivity = scale} , setMinZoom: function(zoom) {that.options.minZoom = zoom} , setMaxZoom: function(zoom) {that.options.maxZoom = zoom} // Zoom event , setBeforeZoom: function(fn) {that.options.beforeZoom = Utils.proxy(fn, that.publicInstance)} , setOnZoom: function(fn) {that.options.onZoom = Utils.proxy(fn, that.publicInstance)} // Zooming , zoom: function(scale) { that.zoomAtPoint(that.svg, SvgUtils.getSvgCenterPoint(that.svg), scale, true) } , zoomBy: function(scale) { that.zoomAtPoint(that.svg, SvgUtils.getSvgCenterPoint(that.svg), scale, false) } , zoomAtPoint: function(scale, point) { that.publicZoomAtPoint(scale, point, true) } , zoomAtPointBy: function(scale, point) { that.publicZoomAtPoint(scale, point, false) } , zoomIn: function() { this.zoomBy(1 + that.options.zoomScaleSensitivity) } , zoomOut: function() { this.zoomBy(1 / (1 + that.options.zoomScaleSensitivity)) } , resetZoom: function() {that.resetZoom()} , getZoom: function() {return that.getZoom()} , fit: function(dropCache) {return that.fit(dropCache)} , center: function(dropCache) {return that.center(dropCache)} } } return this.publicInstance } /** * Stores pairs of instances of SvgPanZoom and SVG * Each pair is represented by an object {svg: SVG, instance: SvgPanZoom} * * @type {Array} */ var instancesStore = [] window.svgPanZoom = function(elementOrSelector, options){ var svg = Utils.getSvg(elementOrSelector) if (svg === null) { return null } else { // Look for existent instance for(var i = instancesStore.length - 1; i >= 0; i--) { if (instancesStore[i].svg === svg) { return instancesStore[i].instance.getPublicInstance() } } // If instance not found - create one instancesStore.push({ svg: svg , instance: new SvgPanZoom(svg, options) }) // Return just pushed instance return instancesStore[instancesStore.length - 1].instance.getPublicInstance() } } })(window, document) },{"./control-icons":1,"./mousewheel":2,"./svg-utilities":4,"./utilities":5}],4:[function(require,module,exports){ module.exports = { /** * Get svg dimensions: width and height * * @param {object} svg * @return {object} {width: 0, height: 0} */ getSvgDimensions: function(svg) { var width = 0 , height = 0 , svgClientRects = svg.getClientRects() // thanks to http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser var isFirefox = typeof InstallTrigger !== 'undefined'; // Firefox has no nice way of detecting SVG size, so we'll check for // width/height from getComputedStyle, specified in pixels, // and if they don't exist, we'll use the parent dimensions. // TODO: check whether this method would be a better method for other browsers too. if (isFirefox) { var svgComputedStyle = window.getComputedStyle(svg, null); width = parseFloat(svgComputedStyle.width) - (parseFloat(svgComputedStyle.borderLeftWidth) + parseFloat(svgComputedStyle.paddingLeft) + parseFloat(svgComputedStyle.borderRightWidth) + parseFloat(svgComputedStyle.paddingRight)); height = parseFloat(svgComputedStyle.height) - (parseFloat(svgComputedStyle.borderTopWidth) + parseFloat(svgComputedStyle.paddingTop) + parseFloat(svgComputedStyle.borderBottomWidth) + parseFloat(svgComputedStyle.paddingBottom)); if (!width || !height) { var parentStyle = window.getComputedStyle(svg.parentElement, null); var parentDimensions = svg.parentElement.getBoundingClientRect(); width = parentDimensions.width - (parseFloat(parentStyle.borderLeftWidth) + parseFloat(parentStyle.paddingLeft) + parseFloat(parentStyle.borderRightWidth) + parseFloat(parentStyle.paddingRight)); height = parentDimensions.height - (parseFloat(parentStyle.borderTopWidth) + parseFloat(parentStyle.paddingTop) + parseFloat(parentStyle.borderBottomWidth) + parseFloat(parentStyle.paddingBottom)); } } else { if (typeof svgClientRects !== 'undefined' && svgClientRects.length > 0) { var svgClientRect = svgClientRects[0]; width = parseFloat(svgClientRect.width); height = parseFloat(svgClientRect.height); } else { var svgBoundingClientRect = svg.getBoundingClientRect(); if (!!svgBoundingClientRect) { width = parseFloat(svgBoundingClientRect.width); height = parseFloat(svgBoundingClientRect.height); } else { throw new Error('Cannot determine SVG width and height.'); } } } return { width: width , height: height } } /** * Gets g.viewport element or creates it if it doesn't exist * @param {object} svg * @return {object} g element */ , getOrCreateViewport: function(svg) { var viewport = svg.querySelector('g.viewport') // If no g container with id 'viewport' exists, create one if (!viewport) { var viewport = document.createElementNS('http://www.w3.org/2000/svg', 'g'); viewport.setAttribute('class', 'viewport'); var svgChildren = svg.childNodes || svg.children; do { viewport.appendChild(svgChildren[0]); } while (svgChildren.length > 0); svg.appendChild(viewport); } return viewport } , setupSvgAttributes: function(svg) { // Setting default attributes svg.setAttribute('xmlns', 'http://www.w3.org/1999/xlink'); svg.setAttributeNS('xmlns', 'xlink', 'http://www.w3.org/1999/xlink'); svg.setAttributeNS('xmlns', 'ev', 'http://www.w3.org/2001/xml-events'); // Needed for Internet Explorer, otherwise the viewport overflows if (svg.parentNode !== null) { var style = svg.getAttribute('style') || ''; if (style.toLowerCase().indexOf('overflow') === -1) { svg.setAttribute('style', 'overflow: hidden; ' + style); } } } /** * Sets the current transform matrix of an element * @param {object} element SVG Element * @param {object} matrix CTM */ , setCTM: function(element, matrix) { var s = 'matrix(' + matrix.a + ',' + matrix.b + ',' + matrix.c + ',' + matrix.d + ',' + matrix.e + ',' + matrix.f + ')'; element.setAttribute('transform', s); } /** * Time-based cache for svg.getScreenCTM(). * Needed because getScreenCTM() is very slow on Firefox (FF 28 at time of writing). * The cache expires every 300ms... this is a pretty safe time because it's only called * when we're zooming, when the screenCTM is unlikely/impossible to change. * * @param {object} svg SVG Element * @return {[type]} [description] */ , getScreenCTMCached: (function() { var svgs = {}; return function(svg) { var cur = Date.now(); if (svgs.hasOwnProperty(svg)) { var cached = svgs[svg]; if (cur - cached.time > 300) { // Cache expired cached.time = cur; cached.ctm = svg.getScreenCTM(); } return cached.ctm; } else { var ctm = svg.getScreenCTM(); svgs[svg] = {time: cur, ctm: ctm}; return ctm; } }; })() /** * Get an SVGPoint of the mouse co-ordinates of the event, relative to the SVG element * * @param {object} svg SVG Element * @param {object} evt Event * @return {object} point */ , getRelativeMousePoint: function(svg, evt) { var point = svg.createSVGPoint() point.x = evt.clientX point.y = evt.clientY return point.matrixTransform(this.getScreenCTMCached(svg).inverse()) } /** * Instantiate an SVGPoint object with given event coordinates * * @param {object} evt Event */ , getEventPoint: function(evt) { var svg = (evt.target.tagName === 'svg' || evt.target.tagName === 'SVG') ? evt.target : evt.target.ownerSVGElement || evt.target.correspondingElement.ownerSVGElement , point = svg.createSVGPoint() point.x = evt.clientX point.y = evt.clientY return point } /** * Get SVG center point * * @param {object} svg SVG Element * @return {object} SVG Point */ , getSvgCenterPoint: function(svg) { var boundingClientRect = svg.getBoundingClientRect() , width = boundingClientRect.width , height = boundingClientRect.height , point = svg.createSVGPoint() point.x = width / 2 point.y = height / 2 return point } } },{}],5:[function(require,module,exports){ module.exports = { /** * Extends an object * * @param {object} target object to extend * @param {object} source object to take properties from * @return {object} extended object */ extend: function(target, source) { target = target || {}; for (var prop in source) { // Go recursively if (this.isObject(source[prop])) { target[prop] = this.extend(target[prop], source[prop]) } else { target[prop] = source[prop] } } return target; } /** * Checks if an object is a DOM element * * @param {object} o HTML element or String * @return {Boolean} returns true if object is a DOM element */ , isElement: function(o){ return ( typeof HTMLElement === "object" ? (o instanceof HTMLElement || o instanceof SVGElement || o instanceof SVGSVGElement) : //DOM2 o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string" ); } /** * Checks if an object is an Object * * @param {object} o Object * @return {Boolean} returns true if object is an Object */ , isObject: function(o){ return Object.prototype.toString.call(o) === '[object Object]'; } /** * Checks if an object is a Function * * @param {object} f Function * @return {Boolean} returns true if object is a Function */ , isFunction: function(f){ return Object.prototype.toString.call(f) === '[object Function]'; } /** * Search for an SVG element * * @param {object|string} elementOrSelector DOM Element or selector String * @return {object|null} SVG or null */ , getSvg: function(elementOrSelector) { var element , svg; if (!this.isElement(elementOrSelector)) { // If selector provided if (typeof elementOrSelector == 'string' || elementOrSelector instanceof String) { // Try to find the element element = document.querySelector(elementOrSelector) if (!element) { throw new Error('Provided selector did not find any elements') return null } } else { throw new Error('Provided selector is not an HTML object nor String') return null } } else { element = elementOrSelector } if (element.tagName.toLowerCase() === 'svg') { svg = element; } else { if (element.tagName.toLowerCase() === 'object') { svg = element.contentDocument.documentElement; } else { if (element.tagName.toLowerCase() === 'embed') { svg = element.getSVGDocument().documentElement; } else { if (element.tagName.toLowerCase() === 'img') { throw new Error('Cannot script an SVG in an "img" element. Please use an "object" element or an in-line SVG.'); } else { throw new Error('Cannot get SVG.'); } return null } } } return svg } /** * Attach a given context to a function * @param {Function} fn Function * @param {object} context Context * @return {Function} Function with certain context */ , proxy: function(fn, context) { return function() { fn.apply(context, arguments) } } /** * Returns object type * Uses toString that returns [object SVGPoint] * And than parses object type from string * * @param {object} o Any object * @return {string} Object type */ , getType: function(o) { return Object.prototype.toString.apply(o).replace(/^\[object\s/, '').replace(/\]$/, '') } } },{}]},{},[3])