text: // svg-pan-zoom v1.0.0
  // https://github.com/tonysamperi/svg-pan-zoom
  (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
  var SvgUtils = require("./svg-utilities");
  
  module.exports = {
    enable: function(instance) {
      // Select (and create if necessary) defs
      var defs = instance.svg.querySelector("defs");
      if (!defs) {
        defs = document.createElementNS(SvgUtils.svgNS, "defs");
        instance.svg.appendChild(defs);
      }
  
      // Check for style element, and create it if it doesn't exist
      var styleEl = defs.querySelector("style#svg-pan-zoom-controls-styles");
      if (!styleEl) {
        var style = document.createElementNS(SvgUtils.svgNS, "style");
        style.setAttribute("id", "svg-pan-zoom-controls-styles");
        style.setAttribute("type", "text/css");
        style.textContent =
          ".svg-pan-zoom-control { cursor: pointer; fill: black; fill-opacity: 0.333; } .svg-pan-zoom-control:hover { fill-opacity: 0.8; } .svg-pan-zoom-control-background { fill: white; fill-opacity: 0.5; } .svg-pan-zoom-control-background { fill-opacity: 0.8; }";
        defs.appendChild(style);
      }
  
      // Zoom Group
      var zoomGroup = document.createElementNS(SvgUtils.svgNS, "g");
      zoomGroup.setAttribute("id", "svg-pan-zoom-controls");
      zoomGroup.setAttribute(
        "transform",
        "translate(" +
          (instance.width - 70) +
          " " +
          (instance.height - 76) +
          ") scale(0.75)"
      );
      zoomGroup.setAttribute("class", "svg-pan-zoom-control");
  
      // Control elements
      zoomGroup.appendChild(this._createZoomIn(instance));
      zoomGroup.appendChild(this._createZoomReset(instance));
      zoomGroup.appendChild(this._createZoomOut(instance));
  
      // Finally append created element
      instance.svg.appendChild(zoomGroup);
  
      // Cache control instance
      instance.controlIcons = zoomGroup;
    },
  
    _createZoomIn: function(instance) {
      var zoomIn = document.createElementNS(SvgUtils.svgNS, "g");
      zoomIn.setAttribute("id", "svg-pan-zoom-zoom-in");
      zoomIn.setAttribute("transform", "translate(30.5 5) scale(0.015)");
      zoomIn.setAttribute("class", "svg-pan-zoom-control");
      zoomIn.addEventListener(
        "click",
        function() {
          instance.getPublicInstance().zoomIn();
        },
        false
      );
      zoomIn.addEventListener(
        "touchstart",
        function() {
          instance.getPublicInstance().zoomIn();
        },
        false
      );
  
      var zoomInBackground = document.createElementNS(SvgUtils.svgNS, "rect"); // TODO change these background space fillers to rounded rectangles so they look prettier
      zoomInBackground.setAttribute("x", "0");
      zoomInBackground.setAttribute("y", "0");
      zoomInBackground.setAttribute("width", "1500"); // larger than expected because the whole group is transformed to scale down
      zoomInBackground.setAttribute("height", "1400");
      zoomInBackground.setAttribute("class", "svg-pan-zoom-control-background");
      zoomIn.appendChild(zoomInBackground);
  
      var zoomInShape = document.createElementNS(SvgUtils.svgNS, "path");
      zoomInShape.setAttribute(
        "d",
        "M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z"
      );
      zoomInShape.setAttribute("class", "svg-pan-zoom-control-element");
      zoomIn.appendChild(zoomInShape);
  
      return zoomIn;
    },
  
    _createZoomReset: function(instance) {
      // reset
      var resetPanZoomControl = document.createElementNS(SvgUtils.svgNS, "g");
      resetPanZoomControl.setAttribute("id", "svg-pan-zoom-reset-pan-zoom");
      resetPanZoomControl.setAttribute("transform", "translate(5 35) scale(0.4)");
      resetPanZoomControl.setAttribute("class", "svg-pan-zoom-control");
      resetPanZoomControl.addEventListener(
        "click",
        function() {
          instance.getPublicInstance().reset();
        },
        false
      );
      resetPanZoomControl.addEventListener(
        "touchstart",
        function() {
          instance.getPublicInstance().reset();
        },
        false
      );
  
      var resetPanZoomControlBackground = document.createElementNS(
        SvgUtils.svgNS,
        "rect"
      ); // TODO change these background space fillers to rounded rectangles so they look prettier
      resetPanZoomControlBackground.setAttribute("x", "2");
      resetPanZoomControlBackground.setAttribute("y", "2");
      resetPanZoomControlBackground.setAttribute("width", "182"); // larger than expected because the whole group is transformed to scale down
      resetPanZoomControlBackground.setAttribute("height", "58");
      resetPanZoomControlBackground.setAttribute(
        "class",
        "svg-pan-zoom-control-background"
      );
      resetPanZoomControl.appendChild(resetPanZoomControlBackground);
  
      var resetPanZoomControlShape1 = document.createElementNS(
        SvgUtils.svgNS,
        "path"
      );
      resetPanZoomControlShape1.setAttribute(
        "d",
        "M33.051,20.632c-0.742-0.406-1.854-0.609-3.338-0.609h-7.969v9.281h7.769c1.543,0,2.701-0.188,3.473-0.562c1.365-0.656,2.048-1.953,2.048-3.891C35.032,22.757,34.372,21.351,33.051,20.632z"
      );
      resetPanZoomControlShape1.setAttribute(
        "class",
        "svg-pan-zoom-control-element"
      );
      resetPanZoomControl.appendChild(resetPanZoomControlShape1);
  
      var resetPanZoomControlShape2 = document.createElementNS(
        SvgUtils.svgNS,
        "path"
      );
      resetPanZoomControlShape2.setAttribute(
        "d",
        "M170.231,0.5H15.847C7.102,0.5,0.5,5.708,0.5,11.84v38.861C0.5,56.833,7.102,61.5,15.847,61.5h154.384c8.745,0,15.269-4.667,15.269-10.798V11.84C185.5,5.708,178.976,0.5,170.231,0.5z M42.837,48.569h-7.969c-0.219-0.766-0.375-1.383-0.469-1.852c-0.188-0.969-0.289-1.961-0.305-2.977l-0.047-3.211c-0.03-2.203-0.41-3.672-1.142-4.406c-0.732-0.734-2.103-1.102-4.113-1.102h-7.05v13.547h-7.055V14.022h16.524c2.361,0.047,4.178,0.344,5.45,0.891c1.272,0.547,2.351,1.352,3.234,2.414c0.731,0.875,1.31,1.844,1.737,2.906s0.64,2.273,0.64,3.633c0,1.641-0.414,3.254-1.242,4.84s-2.195,2.707-4.102,3.363c1.594,0.641,2.723,1.551,3.387,2.73s0.996,2.98,0.996,5.402v2.32c0,1.578,0.063,2.648,0.19,3.211c0.19,0.891,0.635,1.547,1.333,1.969V48.569z M75.579,48.569h-26.18V14.022h25.336v6.117H56.454v7.336h16.781v6H56.454v8.883h19.125V48.569z M104.497,46.331c-2.44,2.086-5.887,3.129-10.34,3.129c-4.548,0-8.125-1.027-10.731-3.082s-3.909-4.879-3.909-8.473h6.891c0.224,1.578,0.662,2.758,1.316,3.539c1.196,1.422,3.246,2.133,6.15,2.133c1.739,0,3.151-0.188,4.236-0.562c2.058-0.719,3.087-2.055,3.087-4.008c0-1.141-0.504-2.023-1.512-2.648c-1.008-0.609-2.607-1.148-4.796-1.617l-3.74-0.82c-3.676-0.812-6.201-1.695-7.576-2.648c-2.328-1.594-3.492-4.086-3.492-7.477c0-3.094,1.139-5.664,3.417-7.711s5.623-3.07,10.036-3.07c3.685,0,6.829,0.965,9.431,2.895c2.602,1.93,3.966,4.73,4.093,8.402h-6.938c-0.128-2.078-1.057-3.555-2.787-4.43c-1.154-0.578-2.587-0.867-4.301-0.867c-1.907,0-3.428,0.375-4.565,1.125c-1.138,0.75-1.706,1.797-1.706,3.141c0,1.234,0.561,2.156,1.682,2.766c0.721,0.406,2.25,0.883,4.589,1.43l6.063,1.43c2.657,0.625,4.648,1.461,5.975,2.508c2.059,1.625,3.089,3.977,3.089,7.055C108.157,41.624,106.937,44.245,104.497,46.331z M139.61,48.569h-26.18V14.022h25.336v6.117h-18.281v7.336h16.781v6h-16.781v8.883h19.125V48.569z M170.337,20.14h-10.336v28.43h-7.266V20.14h-10.383v-6.117h27.984V20.14z"
      );
      resetPanZoomControlShape2.setAttribute(
        "class",
        "svg-pan-zoom-control-element"
      );
      resetPanZoomControl.appendChild(resetPanZoomControlShape2);
  
      return resetPanZoomControl;
    },
  
    _createZoomOut: function(instance) {
      // zoom out
      var zoomOut = document.createElementNS(SvgUtils.svgNS, "g");
      zoomOut.setAttribute("id", "svg-pan-zoom-zoom-out");
      zoomOut.setAttribute("transform", "translate(30.5 70) scale(0.015)");
      zoomOut.setAttribute("class", "svg-pan-zoom-control");
      zoomOut.addEventListener(
        "click",
        function() {
          instance.getPublicInstance().zoomOut();
        },
        false
      );
      zoomOut.addEventListener(
        "touchstart",
        function() {
          instance.getPublicInstance().zoomOut();
        },
        false
      );
  
      var zoomOutBackground = document.createElementNS(SvgUtils.svgNS, "rect"); // TODO change these background space fillers to rounded rectangles so they look prettier
      zoomOutBackground.setAttribute("x", "0");
      zoomOutBackground.setAttribute("y", "0");
      zoomOutBackground.setAttribute("width", "1500"); // larger than expected because the whole group is transformed to scale down
      zoomOutBackground.setAttribute("height", "1400");
      zoomOutBackground.setAttribute("class", "svg-pan-zoom-control-background");
      zoomOut.appendChild(zoomOutBackground);
  
      var zoomOutShape = document.createElementNS(SvgUtils.svgNS, "path");
      zoomOutShape.setAttribute(
        "d",
        "M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z"
      );
      zoomOutShape.setAttribute("class", "svg-pan-zoom-control-element");
      zoomOut.appendChild(zoomOutShape);
  
      return zoomOut;
    },
  
    disable: function(instance) {
      if (instance.controlIcons) {
        instance.controlIcons.parentNode.removeChild(instance.controlIcons);
        instance.controlIcons = null;
      }
    }
  };
  
  },{"./svg-utilities":5}],2:[function(require,module,exports){
  var SvgUtils = require("./svg-utilities"),
    Utils = require("./utilities");
  
  var ShadowViewport = function(viewport, options) {
    this.init(viewport, options);
  };
  
  /**
   * Initialization
   *
   * @param  {SVGElement} viewport
   * @param  {Object} options
   */
  ShadowViewport.prototype.init = function(viewport, options) {
    // DOM Elements
    this.viewport = viewport;
    this.options = options;
  
    // State cache
    this.originalState = { zoom: 1, x: 0, y: 0 };
    this.activeState = { zoom: 1, x: 0, y: 0 };
  
    this.updateCTMCached = Utils.proxy(this.updateCTM, this);
  
    // Create a custom requestAnimationFrame taking in account refreshRate
    this.requestAnimationFrame = Utils.createRequestAnimationFrame(
      this.options.refreshRate
    );
  
    // ViewBox
    this.viewBox = { x: 0, y: 0, width: 0, height: 0 };
    this.cacheViewBox();
  
    // Process CTM
    var newCTM = this.processCTM();
  
    // Update viewport CTM and cache zoom and pan
    this.setCTM(newCTM);
  
    // Update CTM in this frame
    this.updateCTM();
  };
  
  /**
   * Cache initial viewBox value
   * If no viewBox is defined, then use viewport size/position instead for viewBox values
   */
  ShadowViewport.prototype.cacheViewBox = function() {
    var svgViewBox = this.options.svg.getAttribute("viewBox");
  
    if (svgViewBox) {
      var viewBoxValues = svgViewBox
        .split(/[\s\,]/)
        .filter(function(v) {
          return v;
        })
        .map(parseFloat);
  
      // Cache viewbox x and y offset
      this.viewBox.x = viewBoxValues[0];
      this.viewBox.y = viewBoxValues[1];
      this.viewBox.width = viewBoxValues[2];
      this.viewBox.height = viewBoxValues[3];
  
      var zoom = Math.min(
        this.options.width / this.viewBox.width,
        this.options.height / this.viewBox.height
      );
  
      // Update active state
      this.activeState.zoom = zoom;
      this.activeState.x = (this.options.width - this.viewBox.width * zoom) / 2;
      this.activeState.y = (this.options.height - this.viewBox.height * zoom) / 2;
  
      // Force updating CTM
      this.updateCTMOnNextFrame();
  
      this.options.svg.removeAttribute("viewBox");
    } else {
      this.simpleViewBoxCache();
    }
  };
  
  /**
   * Recalculate viewport sizes and update viewBox cache
   */
  ShadowViewport.prototype.simpleViewBoxCache = function() {
    var bBox = this.viewport.getBBox();
  
    this.viewBox.x = bBox.x;
    this.viewBox.y = bBox.y;
    this.viewBox.width = bBox.width;
    this.viewBox.height = bBox.height;
  };
  
  /**
   * Returns a viewbox object. Safe to alter
   *
   * @return {Object} viewbox object
   */
  ShadowViewport.prototype.getViewBox = function() {
    return Utils.extend({}, this.viewBox);
  };
  
  /**
   * Get initial zoom and pan values. Save them into originalState
   * Parses viewBox attribute to alter initial sizes
   *
   * @return {CTM} CTM object based on options
   */
  ShadowViewport.prototype.processCTM = function() {
    var newCTM = this.getCTM();
  
    if (this.options.fit || this.options.contain) {
      var newScale;
      if (this.options.fit) {
        newScale = Math.min(
          this.options.width / this.viewBox.width,
          this.options.height / this.viewBox.height
        );
      } else {
        newScale = Math.max(
          this.options.width / this.viewBox.width,
          this.options.height / this.viewBox.height
        );
      }
  
      newCTM.a = newScale; //x-scale
      newCTM.d = newScale; //y-scale
      newCTM.e = -this.viewBox.x * newScale; //x-transform
      newCTM.f = -this.viewBox.y * newScale; //y-transform
    }
  
    if (this.options.center) {
      var offsetX =
          (this.options.width -
            (this.viewBox.width + this.viewBox.x * 2) * newCTM.a) *
          0.5,
        offsetY =
          (this.options.height -
            (this.viewBox.height + this.viewBox.y * 2) * newCTM.a) *
          0.5;
  
      newCTM.e = offsetX;
      newCTM.f = offsetY;
    }
  
    // Cache initial values. Based on activeState and fix+center opitons
    this.originalState.zoom = newCTM.a;
    this.originalState.x = newCTM.e;
    this.originalState.y = newCTM.f;
  
    return newCTM;
  };
  
  /**
   * Return originalState object. Safe to alter
   *
   * @return {Object}
   */
  ShadowViewport.prototype.getOriginalState = function() {
    return Utils.extend({}, this.originalState);
  };
  
  /**
   * Return actualState object. Safe to alter
   *
   * @return {Object}
   */
  ShadowViewport.prototype.getState = function() {
    return Utils.extend({}, this.activeState);
  };
  
  /**
   * Get zoom scale
   *
   * @return {Float} zoom scale
   */
  ShadowViewport.prototype.getZoom = function() {
    return this.activeState.zoom;
  };
  
  /**
   * Get zoom scale for pubilc usage
   *
   * @return {Float} zoom scale
   */
  ShadowViewport.prototype.getRelativeZoom = function() {
    return this.activeState.zoom / this.originalState.zoom;
  };
  
  /**
   * Compute zoom scale for pubilc usage
   *
   * @return {Float} zoom scale
   */
  ShadowViewport.prototype.computeRelativeZoom = function(scale) {
    return scale / this.originalState.zoom;
  };
  
  /**
   * Get pan
   *
   * @return {Object}
   */
  ShadowViewport.prototype.getPan = function() {
    return { x: this.activeState.x, y: this.activeState.y };
  };
  
  /**
   * Return cached viewport CTM value that can be safely modified
   *
   * @return {SVGMatrix}
   */
  ShadowViewport.prototype.getCTM = function() {
    var safeCTM = this.options.svg.createSVGMatrix();
  
    // Copy values manually as in FF they are not itterable
    safeCTM.a = this.activeState.zoom;
    safeCTM.b = 0;
    safeCTM.c = 0;
    safeCTM.d = this.activeState.zoom;
    safeCTM.e = this.activeState.x;
    safeCTM.f = this.activeState.y;
  
    return safeCTM;
  };
  
  /**
   * Set a new CTM
   *
   * @param {SVGMatrix} newCTM
   */
  ShadowViewport.prototype.setCTM = function(newCTM) {
    var willZoom = this.isZoomDifferent(newCTM),
      willPan = this.isPanDifferent(newCTM);
  
    if (willZoom || willPan) {
      // Before zoom
      if (willZoom) {
        // If returns false then cancel zooming
        if (
          this.options.beforeZoom(
            this.getRelativeZoom(),
            this.computeRelativeZoom(newCTM.a)
          ) === false
        ) {
          newCTM.a = newCTM.d = this.activeState.zoom;
          willZoom = false;
        } else {
          this.updateCache(newCTM);
          this.options.onZoom(this.getRelativeZoom());
        }
      }
  
      // Before pan
      if (willPan) {
        var preventPan = this.options.beforePan(this.getPan(), {
            x: newCTM.e,
            y: newCTM.f
          }),
          // If prevent pan is an object
          preventPanX = false,
          preventPanY = false;
  
        // If prevent pan is Boolean false
        if (preventPan === false) {
          // Set x and y same as before
          newCTM.e = this.getPan().x;
          newCTM.f = this.getPan().y;
  
          preventPanX = preventPanY = true;
        } else if (Utils.isObject(preventPan)) {
          // Check for X axes attribute
          if (preventPan.x === false) {
            // Prevent panning on x axes
            newCTM.e = this.getPan().x;
            preventPanX = true;
          } else if (Utils.isNumber(preventPan.x)) {
            // Set a custom pan value
            newCTM.e = preventPan.x;
          }
  
          // Check for Y axes attribute
          if (preventPan.y === false) {
            // Prevent panning on x axes
            newCTM.f = this.getPan().y;
            preventPanY = true;
          } else if (Utils.isNumber(preventPan.y)) {
            // Set a custom pan value
            newCTM.f = preventPan.y;
          }
        }
  
        // Update willPan flag
        // Check if newCTM is still different
        if ((preventPanX && preventPanY) || !this.isPanDifferent(newCTM)) {
          willPan = false;
        } else {
          this.updateCache(newCTM);
          this.options.onPan(this.getPan());
        }
      }
  
      // Check again if should zoom or pan
      if (willZoom || willPan) {
        this.updateCTMOnNextFrame();
      }
    }
  };
  
  ShadowViewport.prototype.isZoomDifferent = function(newCTM) {
    return this.activeState.zoom !== newCTM.a;
  };
  
  ShadowViewport.prototype.isPanDifferent = function(newCTM) {
    return this.activeState.x !== newCTM.e || this.activeState.y !== newCTM.f;
  };
  
  /**
   * Update cached CTM and active state
   *
   * @param {SVGMatrix} newCTM
   */
  ShadowViewport.prototype.updateCache = function(newCTM) {
    this.activeState.zoom = newCTM.a;
    this.activeState.x = newCTM.e;
    this.activeState.y = newCTM.f;
  };
  
  ShadowViewport.prototype.pendingUpdate = false;
  
  /**
   * Place a request to update CTM on next Frame
   */
  ShadowViewport.prototype.updateCTMOnNextFrame = function() {
    if (!this.pendingUpdate) {
      // Lock
      this.pendingUpdate = true;
  
      // Throttle next update
      this.requestAnimationFrame.call(window, this.updateCTMCached);
    }
  };
  
  /**
   * Update viewport CTM with cached CTM
   */
  ShadowViewport.prototype.updateCTM = function() {
    var ctm = this.getCTM();
  
    // Updates SVG element
    SvgUtils.setCTM(this.viewport, ctm, this.defs);
  
    // Free the lock
    this.pendingUpdate = false;
  
    // Notify about the update
    if (this.options.onUpdatedCTM) {
      this.options.onUpdatedCTM(ctm);
    }
  };
  
  module.exports = function(viewport, options) {
    return new ShadowViewport(viewport, options);
  };
  
  },{"./svg-utilities":5,"./utilities":7}],3:[function(require,module,exports){
  var svgPanZoom = require("./svg-pan-zoom.js");
  
  // UMD module definition
  (function(window, document) {
    // AMD
    if (typeof define === "function" && define.amd) {
      define("svg-pan-zoom", function() {
        return svgPanZoom;
      });
      // CMD
    } else if (typeof module !== "undefined" && module.exports) {
      module.exports = svgPanZoom;
  
      // Browser
      // Keep exporting globally as module.exports is available because of browserify
      window.svgPanZoom = svgPanZoom;
    }
  })(window, document);
  
  },{"./svg-pan-zoom.js":4}],4:[function(require,module,exports){
  var Wheel = require("./uniwheel"),
    ControlIcons = require("./control-icons"),
    Utils = require("./utilities"),
    SvgUtils = require("./svg-utilities"),
    ShadowViewport = require("./shadow-viewport");
  
  var SvgPanZoom = function(svg, options) {
    this.init(svg, options);
  };
  
  var optionsDefaults = {
    viewportSelector: ".svg-pan-zoom_viewport", // Viewport selector. Can be querySelector string or SVGElement
    panEnabled: true, // enable or disable panning (default enabled)
    controlIconsEnabled: false, // insert icons to give user an option in addition to mouse events to control pan/zoom (default disabled)
    zoomEnabled: true, // enable or disable zooming (default enabled)
    dblClickZoomEnabled: true, // enable or disable zooming by double clicking (default enabled)
    mouseWheelZoomEnabled: true, // enable or disable zooming by mouse wheel (default enabled)
    preventMouseEventsDefault: true, // enable or disable preventDefault for mouse events
    zoomScaleSensitivity: 0.1, // Zoom sensitivity
    minZoom: 0.5, // Minimum Zoom level
    maxZoom: 10, // Maximum Zoom level
    fit: true, // enable or disable viewport fit in SVG (default true)
    contain: false, // enable or disable viewport contain the svg (default false)
    center: true, // enable or disable viewport centering in SVG (default true)
    refreshRate: "auto", // Maximum number of frames per second (altering SVG's viewport)
    beforeZoom: null,
    onZoom: null,
    beforePan: null,
    onPan: null,
    customEventsHandler: null,
    eventsListenerElement: null,
    onUpdatedCTM: null
  };
  
  var passiveListenerOption = { passive: true };
  
  SvgPanZoom.prototype.init = function(svg, options) {
    var that = this;
  
    this.svg = svg;
    this.defs = svg.querySelector("defs");
  
    // Add default attributes to SVG
    SvgUtils.setupSvgAttributes(this.svg);
  
    // Set options
    this.options = Utils.extend(Utils.extend({}, optionsDefaults), options);
  
    // Set default state
    this.state = "none";
  
    // Get dimensions
    var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized(
      svg
    );
    this.width = boundingClientRectNormalized.width;
    this.height = boundingClientRectNormalized.height;
  
    // Init shadow viewport
    this.viewport = ShadowViewport(
      SvgUtils.getOrCreateViewport(this.svg, this.options.viewportSelector),
      {
        svg: this.svg,
        width: this.width,
        height: this.height,
        fit: this.options.fit,
        contain: this.options.contain,
        center: this.options.center,
        refreshRate: this.options.refreshRate,
        // Put callbacks into functions as they can change through time
        beforeZoom: function(oldScale, newScale) {
          if (that.viewport && that.options.beforeZoom) {
            return that.options.beforeZoom(oldScale, newScale);
          }
        },
        onZoom: function(scale) {
          if (that.viewport && that.options.onZoom) {
            return that.options.onZoom(scale);
          }
        },
        beforePan: function(oldPoint, newPoint) {
          if (that.viewport && that.options.beforePan) {
            return that.options.beforePan(oldPoint, newPoint);
          }
        },
        onPan: function(point) {
          if (that.viewport && that.options.onPan) {
            return that.options.onPan(point);
          }
        },
        onUpdatedCTM: function(ctm) {
          if (that.viewport && that.options.onUpdatedCTM) {
            return that.options.onUpdatedCTM(ctm);
          }
        }
      }
    );
  
    // Wrap callbacks into public API context
    var publicInstance = this.getPublicInstance();
    publicInstance.setBeforeZoom(this.options.beforeZoom);
    publicInstance.setOnZoom(this.options.onZoom);
    publicInstance.setBeforePan(this.options.beforePan);
    publicInstance.setOnPan(this.options.onPan);
    publicInstance.setOnUpdatedCTM(this.options.onUpdatedCTM);
  
    if (this.options.controlIconsEnabled) {
      ControlIcons.enable(this);
    }
  
    // Init events handlers
    this.lastMouseWheelEventTime = Date.now();
    this.setupHandlers();
  };
  
  /**
   * Register event handlers
   */
  SvgPanZoom.prototype.setupHandlers = function() {
    var that = this,
      prevEvt = null; // use for touchstart event to detect double tap
  
    this.eventListeners = {
      // Mouse down group
      mousedown: function(evt) {
        var result = that.handleMouseDown(evt, prevEvt);
        prevEvt = evt;
        return result;
      },
      touchstart: function(evt) {
        var result = that.handleMouseDown(evt, prevEvt);
        prevEvt = evt;
        return result;
      },
  
      // Mouse up group
      mouseup: function(evt) {
        return that.handleMouseUp(evt);
      },
      touchend: function(evt) {
        return that.handleMouseUp(evt);
      },
  
      // Mouse move group
      mousemove: function(evt) {
        return that.handleMouseMove(evt);
      },
      touchmove: function(evt) {
        return that.handleMouseMove(evt);
      },
  
      // Mouse leave group
      mouseleave: function(evt) {
        return that.handleMouseUp(evt);
      },
      touchleave: function(evt) {
        return that.handleMouseUp(evt);
      },
      touchcancel: function(evt) {
        return that.handleMouseUp(evt);
      }
    };
  
    // Init custom events handler if available
    // eslint-disable-next-line eqeqeq
    if (this.options.customEventsHandler != null) {
      this.options.customEventsHandler.init({
        svgElement: this.svg,
        eventsListenerElement: this.options.eventsListenerElement,
        instance: this.getPublicInstance()
      });
  
      // Custom event handler may halt builtin listeners
      var haltEventListeners = this.options.customEventsHandler
        .haltEventListeners;
      if (haltEventListeners && haltEventListeners.length) {
        for (var i = haltEventListeners.length - 1; i >= 0; i--) {
          if (this.eventListeners.hasOwnProperty(haltEventListeners[i])) {
            delete this.eventListeners[haltEventListeners[i]];
          }
        }
      }
    }
  
    // Bind eventListeners
    for (var event in this.eventListeners) {
      // Attach event to eventsListenerElement or SVG if not available
      (this.options.eventsListenerElement || this.svg).addEventListener(
        event,
        this.eventListeners[event],
        !this.options.preventMouseEventsDefault ? passiveListenerOption : false
      );
    }
  
    // Zoom using mouse wheel
    if (this.options.mouseWheelZoomEnabled) {
      this.options.mouseWheelZoomEnabled = false; // set to false as enable will set it back to true
      this.enableMouseWheelZoom();
    }
  };
  
  /**
   * Enable ability to zoom using mouse wheel
   */
  SvgPanZoom.prototype.enableMouseWheelZoom = function() {
    if (!this.options.mouseWheelZoomEnabled) {
      var that = this;
  
      // Mouse wheel listener
      this.wheelListener = function(evt) {
        return that.handleMouseWheel(evt);
      };
  
      // Bind wheelListener
      var isPassiveListener = !this.options.preventMouseEventsDefault;
      Wheel.on(
        this.options.eventsListenerElement || this.svg,
        this.wheelListener,
        isPassiveListener
      );
  
      this.options.mouseWheelZoomEnabled = true;
    }
  };
  
  /**
   * Disable ability to zoom using mouse wheel
   */
  SvgPanZoom.prototype.disableMouseWheelZoom = function() {
    if (this.options.mouseWheelZoomEnabled) {
      var isPassiveListener = !this.options.preventMouseEventsDefault;
      Wheel.off(
        this.options.eventsListenerElement || this.svg,
        this.wheelListener,
        isPassiveListener
      );
      this.options.mouseWheelZoomEnabled = false;
    }
  };
  
  /**
   * Handle mouse wheel event
   *
   * @param  {Event} evt
   */
  SvgPanZoom.prototype.handleMouseWheel = function(evt) {
    if (!this.options.zoomEnabled || this.state !== "none") {
      return;
    }
  
    if (this.options.preventMouseEventsDefault) {
      if (evt.preventDefault) {
        evt.preventDefault();
      } else {
        evt.returnValue = false;
      }
    }
  
    // Default delta in case that deltaY is not available
    var delta = evt.deltaY || 1,
      timeDelta = Date.now() - this.lastMouseWheelEventTime,
      divider = 3 + Math.max(0, 30 - timeDelta);
  
    // Update cache
    this.lastMouseWheelEventTime = Date.now();
  
    // Make empirical adjustments for browsers that give deltaY in pixels (deltaMode=0)
    if ("deltaMode" in evt && evt.deltaMode === 0 && evt.wheelDelta) {
      delta = evt.deltaY === 0 ? 0 : Math.abs(evt.wheelDelta) / evt.deltaY;
    }
  
    delta =
      -0.3 < delta && delta < 0.3
        ? delta
        : ((delta > 0 ? 1 : -1) * Math.log(Math.abs(delta) + 10)) / divider;
  
    var inversedScreenCTM = this.svg.getScreenCTM().inverse(),
      relativeMousePoint = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(
        inversedScreenCTM
      ),
      zoom = Math.pow(1 + this.options.zoomScaleSensitivity, -1 * delta); // multiplying by neg. 1 so as to make zoom in/out behavior match Google maps behavior
  
    this.zoomAtPoint(zoom, relativeMousePoint);
  };
  
  /**
   * Zoom in at a SVG point
   *
   * @param  {SVGPoint} point
   * @param  {Float} zoomScale    Number representing how much to zoom
   * @param  {Boolean} zoomAbsolute Default false. If true, zoomScale is treated as an absolute value.
   *                                Otherwise, zoomScale is treated as a multiplied (e.g. 1.10 would zoom in 10%)
   */
  SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute) {
    var originalState = this.viewport.getOriginalState();
  
    if (!zoomAbsolute) {
      // Fit zoomScale in set bounds
      if (
        this.getZoom() * zoomScale <
        this.options.minZoom * originalState.zoom
      ) {
        zoomScale = (this.options.minZoom * originalState.zoom) / this.getZoom();
      } else if (
        this.getZoom() * zoomScale >
        this.options.maxZoom * originalState.zoom
      ) {
        zoomScale = (this.options.maxZoom * originalState.zoom) / this.getZoom();
      }
    } else {
      // Fit zoomScale in set bounds
      zoomScale = Math.max(
        this.options.minZoom * originalState.zoom,
        Math.min(this.options.maxZoom * originalState.zoom, zoomScale)
      );
      // Find relative scale to achieve desired scale
      zoomScale = zoomScale / this.getZoom();
    }
  
    var oldCTM = this.viewport.getCTM(),
      relativePoint = point.matrixTransform(oldCTM.inverse()),
      modifier = this.svg
        .createSVGMatrix()
        .translate(relativePoint.x, relativePoint.y)
        .scale(zoomScale)
        .translate(-relativePoint.x, -relativePoint.y),
      newCTM = oldCTM.multiply(modifier);
  
    if (newCTM.a !== oldCTM.a) {
      this.viewport.setCTM(newCTM);
    }
  };
  
  /**
   * Zoom at center point
   *
   * @param  {Float} scale
   * @param  {Boolean} absolute Marks zoom scale as relative or absolute
   */
  SvgPanZoom.prototype.zoom = function(scale, absolute) {
    this.zoomAtPoint(
      scale,
      SvgUtils.getSvgCenterPoint(this.svg, this.width, this.height),
      absolute
    );
  };
  
  /**
   * Zoom used by public instance
   *
   * @param  {Float} scale
   * @param  {Boolean} absolute Marks zoom scale as relative or absolute
   */
  SvgPanZoom.prototype.publicZoom = function(scale, absolute) {
    if (absolute) {
      scale = this.computeFromRelativeZoom(scale);
    }
  
    this.zoom(scale, absolute);
  };
  
  /**
   * Zoom at point used by public instance
   *
   * @param  {Float} scale
   * @param  {SVGPoint|Object} point    An object that has x and y attributes
   * @param  {Boolean} absolute Marks zoom scale as relative or absolute
   */
  SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) {
    if (absolute) {
      // Transform zoom into a relative value
      scale = this.computeFromRelativeZoom(scale);
    }
  
    // If not a SVGPoint but has x and y then create a SVGPoint
    if (Utils.getType(point) !== "SVGPoint") {
      if ("x" in point && "y" in point) {
        point = SvgUtils.createSVGPoint(this.svg, point.x, point.y);
      } else {
        throw new Error("Given point is invalid");
      }
    }
  
    this.zoomAtPoint(scale, point, absolute);
  };
  
  /**
   * Get zoom scale
   *
   * @return {Float} zoom scale
   */
  SvgPanZoom.prototype.getZoom = function() {
    return this.viewport.getZoom();
  };
  
  /**
   * Get zoom scale for public usage
   *
   * @return {Float} zoom scale
   */
  SvgPanZoom.prototype.getRelativeZoom = function() {
    return this.viewport.getRelativeZoom();
  };
  
  /**
   * Compute actual zoom from public zoom
   *
   * @param  {Float} zoom
   * @return {Float} zoom scale
   */
  SvgPanZoom.prototype.computeFromRelativeZoom = function(zoom) {
    return zoom * this.viewport.getOriginalState().zoom;
  };
  
  /**
   * Set zoom to initial state
   */
  SvgPanZoom.prototype.resetZoom = function() {
    var originalState = this.viewport.getOriginalState();
  
    this.zoom(originalState.zoom, true);
  };
  
  /**
   * Set pan to initial state
   */
  SvgPanZoom.prototype.resetPan = function() {
    this.pan(this.viewport.getOriginalState());
  };
  
  /**
   * Set pan and zoom to initial state
   */
  SvgPanZoom.prototype.reset = function() {
    this.resetZoom();
    this.resetPan();
  };
  
  /**
   * Handle double click event
   * See handleMouseDown() for alternate detection method
   *
   * @param {Event} evt
   */
  SvgPanZoom.prototype.handleDblClick = function(evt) {
    if (this.options.preventMouseEventsDefault) {
      if (evt.preventDefault) {
        evt.preventDefault();
      } else {
        evt.returnValue = false;
      }
    }
  
    // Check if target was a control button
    if (this.options.controlIconsEnabled) {
      var targetClass = evt.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.getEventPoint(evt, this.svg).matrixTransform(
      this.svg.getScreenCTM().inverse()
    );
    this.zoomAtPoint(zoomFactor, point);
  };
  
  /**
   * Handle click event
   *
   * @param {Event} evt
   */
  SvgPanZoom.prototype.handleMouseDown = function(evt, prevEvt) {
    if (this.options.preventMouseEventsDefault) {
      if (evt.preventDefault) {
        evt.preventDefault();
      } else {
        evt.returnValue = false;
      }
    }
  
    Utils.mouseAndTouchNormalize(evt, this.svg);
  
    // Double click detection; more consistent than ondblclick
    if (this.options.dblClickZoomEnabled && Utils.isDblClick(evt, prevEvt)) {
      this.handleDblClick(evt);
    } else {
      // Pan mode
      this.state = "pan";
      this.firstEventCTM = this.viewport.getCTM();
      this.stateOrigin = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(
        this.firstEventCTM.inverse()
      );
    }
  };
  
  /**
   * Handle mouse move event
   *
   * @param  {Event} evt
   */
  SvgPanZoom.prototype.handleMouseMove = function(evt) {
    if (this.options.preventMouseEventsDefault) {
      if (evt.preventDefault) {
        evt.preventDefault();
      } else {
        evt.returnValue = false;
      }
    }
  
    if (this.state === "pan" && this.options.panEnabled) {
      // Pan mode
      var point = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(
          this.firstEventCTM.inverse()
        ),
        viewportCTM = this.firstEventCTM.translate(
          point.x - this.stateOrigin.x,
          point.y - this.stateOrigin.y
        );
  
      this.viewport.setCTM(viewportCTM);
    }
  };
  
  /**
   * Handle mouse button release event
   *
   * @param {Event} evt
   */
  SvgPanZoom.prototype.handleMouseUp = function(evt) {
    if (this.options.preventMouseEventsDefault) {
      if (evt.preventDefault) {
        evt.preventDefault();
      } else {
        evt.returnValue = false;
      }
    }
  
    if (this.state === "pan") {
      // Quit pan mode
      this.state = "none";
    }
  };
  
  /**
   * Adjust viewport size (only) so it will fit in SVG
   * Does not center image
   */
  SvgPanZoom.prototype.fit = function() {
    var viewBox = this.viewport.getViewBox(),
      newScale = Math.min(
        this.width / viewBox.width,
        this.height / viewBox.height
      );
  
    this.zoom(newScale, true);
  };
  
  /**
   * Adjust viewport size (only) so it will contain the SVG
   * Does not center image
   */
  SvgPanZoom.prototype.contain = function() {
    var viewBox = this.viewport.getViewBox(),
      newScale = Math.max(
        this.width / viewBox.width,
        this.height / viewBox.height
      );
  
    this.zoom(newScale, true);
  };
  
  /**
   * Adjust viewport pan (only) so it will be centered in SVG
   * Does not zoom/fit/contain image
   */
  SvgPanZoom.prototype.center = function() {
    var viewBox = this.viewport.getViewBox(),
      offsetX =
        (this.width - (viewBox.width + viewBox.x * 2) * this.getZoom()) * 0.5,
      offsetY =
        (this.height - (viewBox.height + viewBox.y * 2) * this.getZoom()) * 0.5;
  
    this.getPublicInstance().pan({ x: offsetX, y: offsetY });
  };
  
  /**
   * Update content cached BorderBox
   * Use when viewport contents change
   */
  SvgPanZoom.prototype.updateBBox = function() {
    this.viewport.simpleViewBoxCache();
  };
  
  /**
   * Pan to a rendered position
   *
   * @param  {Object} point {x: 0, y: 0}
   */
  SvgPanZoom.prototype.pan = function(point) {
    var viewportCTM = this.viewport.getCTM();
    viewportCTM.e = point.x;
    viewportCTM.f = point.y;
    this.viewport.setCTM(viewportCTM);
  };
  
  /**
   * Relatively pan the graph by a specified rendered position vector
   *
   * @param  {Object} point {x: 0, y: 0}
   */
  SvgPanZoom.prototype.panBy = function(point) {
    var viewportCTM = this.viewport.getCTM();
    viewportCTM.e += point.x;
    viewportCTM.f += point.y;
    this.viewport.setCTM(viewportCTM);
  };
  
  /**
   * Get pan vector
   *
   * @return {Object} {x: 0, y: 0}
   */
  SvgPanZoom.prototype.getPan = function() {
    var state = this.viewport.getState();
  
    return { x: state.x, y: state.y };
  };
  
  /**
   * Recalculates cached svg dimensions and controls position
   */
  SvgPanZoom.prototype.resize = function() {
    // Get dimensions
    var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized(
      this.svg
    );
    this.width = boundingClientRectNormalized.width;
    this.height = boundingClientRectNormalized.height;
  
    // Recalculate original state
    var viewport = this.viewport;
    viewport.options.width = this.width;
    viewport.options.height = this.height;
    viewport.processCTM();
  
    // Reposition control icons by re-enabling them
    if (this.options.controlIconsEnabled) {
      this.getPublicInstance().disableControlIcons();
      this.getPublicInstance().enableControlIcons();
    }
  };
  
  /**
   * Unbind mouse events, free callbacks and destroy public instance
   */
  SvgPanZoom.prototype.destroy = function() {
    var that = this;
  
    // Free callbacks
    this.beforeZoom = null;
    this.onZoom = null;
    this.beforePan = null;
    this.onPan = null;
    this.onUpdatedCTM = null;
  
    // Destroy custom event handlers
    // eslint-disable-next-line eqeqeq
    if (this.options.customEventsHandler != null) {
      this.options.customEventsHandler.destroy({
        svgElement: this.svg,
        eventsListenerElement: this.options.eventsListenerElement,
        instance: this.getPublicInstance()
      });
    }
  
    // Unbind eventListeners
    for (var event in this.eventListeners) {
      (this.options.eventsListenerElement || this.svg).removeEventListener(
        event,
        this.eventListeners[event],
        !this.options.preventMouseEventsDefault ? passiveListenerOption : false
      );
    }
  
    // Unbind wheelListener
    this.disableMouseWheelZoom();
  
    // Remove control icons
    this.getPublicInstance().disableControlIcons();
  
    // Reset zoom and pan
    this.reset();
  
    // Remove instance from instancesStore
    instancesStore = instancesStore.filter(function(instance) {
      return instance.svg !== that.svg;
    });
  
    // Delete options and its contents
    delete this.options;
  
    // Delete viewport to make public shadow viewport functions uncallable
    delete this.viewport;
  
    // Destroy public instance and rewrite getPublicInstance
    delete this.publicInstance;
    delete this.pi;
    this.getPublicInstance = function() {
      return null;
    };
  };
  
  /**
   * Returns a public instance object
   *
   * @return {Object} Public instance object
   */
  SvgPanZoom.prototype.getPublicInstance = function() {
    var that = this;
  
    // Create cache
    if (!this.publicInstance) {
      this.publicInstance = this.pi = {
        // Pan
        enablePan: function() {
          that.options.panEnabled = true;
          return that.pi;
        },
        disablePan: function() {
          that.options.panEnabled = false;
          return that.pi;
        },
        isPanEnabled: function() {
          return !!that.options.panEnabled;
        },
        pan: function(point) {
          that.pan(point);
          return that.pi;
        },
        panBy: function(point) {
          that.panBy(point);
          return that.pi;
        },
        getPan: function() {
          return that.getPan();
        },
        // Pan event
        setBeforePan: function(fn) {
          that.options.beforePan =
            fn === null ? null : Utils.proxy(fn, that.publicInstance);
          return that.pi;
        },
        setOnPan: function(fn) {
          that.options.onPan =
            fn === null ? null : Utils.proxy(fn, that.publicInstance);
          return that.pi;
        },
        // Zoom and Control Icons
        enableZoom: function() {
          that.options.zoomEnabled = true;
          return that.pi;
        },
        disableZoom: function() {
          that.options.zoomEnabled = false;
          return that.pi;
        },
        isZoomEnabled: function() {
          return !!that.options.zoomEnabled;
        },
        enableControlIcons: function() {
          if (!that.options.controlIconsEnabled) {
            that.options.controlIconsEnabled = true;
            ControlIcons.enable(that);
          }
          return that.pi;
        },
        disableControlIcons: function() {
          if (that.options.controlIconsEnabled) {
            that.options.controlIconsEnabled = false;
            ControlIcons.disable(that);
          }
          return that.pi;
        },
        isControlIconsEnabled: function() {
          return !!that.options.controlIconsEnabled;
        },
        // Double click zoom
        enableDblClickZoom: function() {
          that.options.dblClickZoomEnabled = true;
          return that.pi;
        },
        disableDblClickZoom: function() {
          that.options.dblClickZoomEnabled = false;
          return that.pi;
        },
        isDblClickZoomEnabled: function() {
          return !!that.options.dblClickZoomEnabled;
        },
        // Mouse wheel zoom
        enableMouseWheelZoom: function() {
          that.enableMouseWheelZoom();
          return that.pi;
        },
        disableMouseWheelZoom: function() {
          that.disableMouseWheelZoom();
          return that.pi;
        },
        isMouseWheelZoomEnabled: function() {
          return !!that.options.mouseWheelZoomEnabled;
        },
        // Zoom scale and bounds
        setZoomScaleSensitivity: function(scale) {
          that.options.zoomScaleSensitivity = scale;
          return that.pi;
        },
        setMinZoom: function(zoom) {
          that.options.minZoom = zoom;
          return that.pi;
        },
        setMaxZoom: function(zoom) {
          that.options.maxZoom = zoom;
          return that.pi;
        },
        // Zoom event
        setBeforeZoom: function(fn) {
          that.options.beforeZoom =
            fn === null ? null : Utils.proxy(fn, that.publicInstance);
          return that.pi;
        },
        setOnZoom: function(fn) {
          that.options.onZoom =
            fn === null ? null : Utils.proxy(fn, that.publicInstance);
          return that.pi;
        },
        // Zooming
        zoom: function(scale) {
          that.publicZoom(scale, true);
          return that.pi;
        },
        zoomBy: function(scale) {
          that.publicZoom(scale, false);
          return that.pi;
        },
        zoomAtPoint: function(scale, point) {
          that.publicZoomAtPoint(scale, point, true);
          return that.pi;
        },
        zoomAtPointBy: function(scale, point) {
          that.publicZoomAtPoint(scale, point, false);
          return that.pi;
        },
        zoomIn: function() {
          this.zoomBy(1 + that.options.zoomScaleSensitivity);
          return that.pi;
        },
        zoomOut: function() {
          this.zoomBy(1 / (1 + that.options.zoomScaleSensitivity));
          return that.pi;
        },
        getZoom: function() {
          return that.getRelativeZoom();
        },
        // CTM update
        setOnUpdatedCTM: function(fn) {
          that.options.onUpdatedCTM =
            fn === null ? null : Utils.proxy(fn, that.publicInstance);
          return that.pi;
        },
        // Reset
        resetZoom: function() {
          that.resetZoom();
          return that.pi;
        },
        resetPan: function() {
          that.resetPan();
          return that.pi;
        },
        reset: function() {
          that.reset();
          return that.pi;
        },
        // Fit, Contain and Center
        fit: function() {
          that.fit();
          return that.pi;
        },
        contain: function() {
          that.contain();
          return that.pi;
        },
        center: function() {
          that.center();
          return that.pi;
        },
        // Size and Resize
        updateBBox: function() {
          that.updateBBox();
          return that.pi;
        },
        resize: function() {
          that.resize();
          return that.pi;
        },
        getSizes: function() {
          return {
            width: that.width,
            height: that.height,
            realZoom: that.getZoom(),
            viewBox: that.viewport.getViewBox()
          };
        },
        // Destroy
        destroy: function() {
          that.destroy();
          return that.pi;
        }
      };
    }
  
    return this.publicInstance;
  };
  
  /**
   * Stores pairs of instances of SvgPanZoom and SVG
   * Each pair is represented by an object {svg: SVGSVGElement, instance: SvgPanZoom}
   *
   * @type {Array}
   */
  var instancesStore = [];
  
  var 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();
    }
  };
  
  module.exports = svgPanZoom;
  
  },{"./control-icons":1,"./shadow-viewport":2,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(require,module,exports){
  var Utils = require("./utilities"),
    _browser = "unknown";
  
  // http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
  if (/*@cc_on!@*/ false || !!document.documentMode) {
    // internet explorer
    _browser = "ie";
  }
  
  module.exports = {
    svgNS: "http://www.w3.org/2000/svg",
    xmlNS: "http://www.w3.org/XML/1998/namespace",
    xmlnsNS: "http://www.w3.org/2000/xmlns/",
    xlinkNS: "http://www.w3.org/1999/xlink",
    evNS: "http://www.w3.org/2001/xml-events",
  
    /**
     * Get svg dimensions: width and height
     *
     * @param  {SVGSVGElement} svg
     * @return {Object}     {width: 0, height: 0}
     */
    getBoundingClientRectNormalized: function(svg) {
      if (svg.clientWidth && svg.clientHeight) {
        return { width: svg.clientWidth, height: svg.clientHeight };
      } else if (!!svg.getBoundingClientRect()) {
        return svg.getBoundingClientRect();
      } else {
        throw new Error("Cannot get BoundingClientRect for SVG.");
      }
    },
  
    /**
     * Gets g element with class of "viewport" or creates it if it doesn't exist
     *
     * @param  {SVGSVGElement} svg
     * @return {SVGElement}     g (group) element
     */
    getOrCreateViewport: function(svg, selector) {
      var viewport = null;
  
      if (Utils.isElement(selector)) {
        viewport = selector;
      } else {
        viewport = svg.querySelector(selector);
      }
  
      // Check if there is just one main group in SVG
      if (!viewport) {
        var childNodes = Array.prototype.slice
          .call(svg.childNodes || svg.children)
          .filter(function(el) {
            return el.nodeName !== "defs" && el.nodeName !== "#text";
          });
  
        // Node name should be SVGGElement and should have no transform attribute
        // Groups with transform are not used as viewport because it involves parsing of all transform possibilities
        if (
          childNodes.length === 1 &&
          childNodes[0].nodeName === "g" &&
          childNodes[0].getAttribute("transform") === null
        ) {
          viewport = childNodes[0];
        }
      }
  
      // If no favorable group element exists then create one
      if (!viewport) {
        var viewportId =
          "viewport-" + new Date().toISOString().replace(/\D/g, "");
        viewport = document.createElementNS(this.svgNS, "g");
        viewport.setAttribute("id", viewportId);
  
        // Internet Explorer (all versions?) can't use childNodes, but other browsers prefer (require?) using childNodes
        var svgChildren = svg.childNodes || svg.children;
        if (!!svgChildren && svgChildren.length > 0) {
          for (var i = svgChildren.length; i > 0; i--) {
            // Move everything into viewport except defs
            if (svgChildren[svgChildren.length - i].nodeName !== "defs") {
              viewport.appendChild(svgChildren[svgChildren.length - i]);
            }
          }
        }
        svg.appendChild(viewport);
      }
  
      // Parse class names
      var classNames = [];
      if (viewport.getAttribute("class")) {
        classNames = viewport.getAttribute("class").split(" ");
      }
  
      // Set class (if not set already)
      if (!~classNames.indexOf("svg-pan-zoom_viewport")) {
        classNames.push("svg-pan-zoom_viewport");
        viewport.setAttribute("class", classNames.join(" "));
      }
  
      return viewport;
    },
  
    /**
     * Set SVG attributes
     *
     * @param  {SVGSVGElement} svg
     */
    setupSvgAttributes: function(svg) {
      // Setting default attributes
      svg.setAttribute("xmlns", this.svgNS);
      svg.setAttributeNS(this.xmlnsNS, "xmlns:xlink", this.xlinkNS);
      svg.setAttributeNS(this.xmlnsNS, "xmlns:ev", this.evNS);
  
      // 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);
        }
      }
    },
  
    /**
     * How long Internet Explorer takes to finish updating its display (ms).
     */
    internetExplorerRedisplayInterval: 300,
  
    /**
     * Forces the browser to redisplay all SVG elements that rely on an
     * element defined in a 'defs' section. It works globally, for every
     * available defs element on the page.
     * The throttling is intentionally global.
     *
     * This is only needed for IE. It is as a hack to make markers (and 'use' elements?)
     * visible after pan/zoom when there are multiple SVGs on the page.
     * See bug report: https://connect.microsoft.com/IE/feedback/details/781964/
     * also see svg-pan-zoom issue: https://github.com/ariutta/svg-pan-zoom/issues/62
     */
    refreshDefsGlobal: Utils.throttle(
      function() {
        var allDefs = document.querySelectorAll("defs");
        var allDefsCount = allDefs.length;
        for (var i = 0; i < allDefsCount; i++) {
          var thisDefs = allDefs[i];
          thisDefs.parentNode.insertBefore(thisDefs, thisDefs);
        }
      },
      this ? this.internetExplorerRedisplayInterval : null
    ),
  
    /**
     * Sets the current transform matrix of an element
     *
     * @param {SVGElement} element
     * @param {SVGMatrix} matrix  CTM
     * @param {SVGElement} defs
     */
    setCTM: function(element, matrix, defs) {
      var that = this,
        s =
          "matrix(" +
          matrix.a +
          "," +
          matrix.b +
          "," +
          matrix.c +
          "," +
          matrix.d +
          "," +
          matrix.e +
          "," +
          matrix.f +
          ")";
  
      element.setAttributeNS(null, "transform", s);
      if ("transform" in element.style) {
        element.style.transform = s;
      } else if ("-ms-transform" in element.style) {
        element.style["-ms-transform"] = s;
      } else if ("-webkit-transform" in element.style) {
        element.style["-webkit-transform"] = s;
      }
  
      // IE has a bug that makes markers disappear on zoom (when the matrix "a" and/or "d" elements change)
      // see http://stackoverflow.com/questions/17654578/svg-marker-does-not-work-in-ie9-10
      // and http://srndolha.wordpress.com/2013/11/25/svg-line-markers-may-disappear-in-internet-explorer-11/
      if (_browser === "ie" && !!defs) {
        // this refresh is intended for redisplaying the SVG during zooming
        defs.parentNode.insertBefore(defs, defs);
        // this refresh is intended for redisplaying the other SVGs on a page when panning a given SVG
        // it is also needed for the given SVG itself, on zoomEnd, if the SVG contains any markers that
        // are located under any other element(s).
        window.setTimeout(function() {
          that.refreshDefsGlobal();
        }, that.internetExplorerRedisplayInterval);
      }
    },
  
    /**
     * Instantiate an SVGPoint object with given event coordinates
     *
     * @param {Event} evt
     * @param  {SVGSVGElement} svg
     * @return {SVGPoint}     point
     */
    getEventPoint: function(evt, svg) {
      var point = svg.createSVGPoint();
  
      Utils.mouseAndTouchNormalize(evt, svg);
  
      point.x = evt.clientX;
      point.y = evt.clientY;
  
      return point;
    },
  
    /**
     * Get SVG center point
     *
     * @param  {SVGSVGElement} svg
     * @return {SVGPoint}
     */
    getSvgCenterPoint: function(svg, width, height) {
      return this.createSVGPoint(svg, width / 2, height / 2);
    },
  
    /**
     * Create a SVGPoint with given x and y
     *
     * @param  {SVGSVGElement} svg
     * @param  {Number} x
     * @param  {Number} y
     * @return {SVGPoint}
     */
    createSVGPoint: function(svg, x, y) {
      var point = svg.createSVGPoint();
      point.x = x;
      point.y = y;
  
      return point;
    }
  };
  
  },{"./utilities":7}],6:[function(require,module,exports){
  // uniwheel 0.1.2 (customized)
  // A unified cross browser mouse wheel event handler
  // https://github.com/teemualap/uniwheel
  
  module.exports = (function(){
  
    //Full details: https://developer.mozilla.org/en-US/docs/Web/Reference/Events/wheel
  
    var prefix = "", _addEventListener, _removeEventListener, support, fns = [];
    var passiveOption = {passive: true};
  
    // detect event model
    if ( window.addEventListener ) {
      _addEventListener = "addEventListener";
      _removeEventListener = "removeEventListener";
    } else {
      _addEventListener = "attachEvent";
      _removeEventListener = "detachEvent";
      prefix = "on";
    }
  
    // detect available wheel event
    support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
              document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
              "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox
  
  
    function createCallback(element,callback) {
  
      var fn = function(originalEvent) {
  
        !originalEvent && ( originalEvent = window.event );
  
        // create a normalized event object
        var event = {
          // keep a ref to the original event object
          originalEvent: originalEvent,
          target: originalEvent.target || originalEvent.srcElement,
          type: "wheel",
          deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
          deltaX: 0,
          delatZ: 0,
          preventDefault: function() {
            originalEvent.preventDefault ?
              originalEvent.preventDefault() :
              originalEvent.returnValue = false;
          }
        };
  
        // calculate deltaY (and deltaX) according to the event
        if ( support == "mousewheel" ) {
          event.deltaY = - 1/40 * originalEvent.wheelDelta;
          // Webkit also support wheelDeltaX
          originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX );
        } else {
          event.deltaY = originalEvent.detail;
        }
  
        // it's time to fire the callback
        return callback( event );
  
      };
  
      fns.push({
        element: element,
        fn: fn,
      });
  
      return fn;
    }
  
    function getCallback(element) {
      for (var i = 0; i < fns.length; i++) {
        if (fns[i].element === element) {
          return fns[i].fn;
        }
      }
      return function(){};
    }
  
    function removeCallback(element) {
      for (var i = 0; i < fns.length; i++) {
        if (fns[i].element === element) {
          return fns.splice(i,1);
        }
      }
    }
  
    function _addWheelListener(elem, eventName, callback, isPassiveListener ) {
      var cb;
  
      if (support === "wheel") {
        cb = callback;
      } else {
        cb = createCallback(elem, callback);
      }
  
      elem[_addEventListener](prefix + eventName, cb, isPassiveListener ? passiveOption : false);
    }
  
    function _removeWheelListener(elem, eventName, callback, isPassiveListener ) {
  
      var cb;
  
      if (support === "wheel") {
        cb = callback;
      } else {
        cb = getCallback(elem);
      }
  
      elem[_removeEventListener](prefix + eventName, cb, isPassiveListener ? passiveOption : false);
  
      removeCallback(elem);
    }
  
    function addWheelListener( elem, callback, isPassiveListener ) {
      _addWheelListener(elem, support, callback, isPassiveListener );
  
      // handle MozMousePixelScroll in older Firefox
      if( support == "DOMMouseScroll" ) {
        _addWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener );
      }
    }
  
    function removeWheelListener(elem, callback, isPassiveListener){
      _removeWheelListener(elem, support, callback, isPassiveListener);
  
      // handle MozMousePixelScroll in older Firefox
      if( support == "DOMMouseScroll" ) {
        _removeWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener);
      }
    }
  
    return {
      on: addWheelListener,
      off: removeWheelListener
    };
  
  })();
  
  },{}],7:[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 (
        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 variable is Number
     *
     * @param  {Integer|Float}  n
     * @return {Boolean}   returns true if variable is Number
     */
    isNumber: function(n) {
      return !isNaN(parseFloat(n)) && isFinite(n);
    },
  
    /**
     * 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. Selector: " +
                elementOrSelector
            );
            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() {
        return 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(/\]$/, "");
    },
  
    /**
     * If it is a touch event than add clientX and clientY to event object
     *
     * @param  {Event} evt
     * @param  {SVGSVGElement} svg
     */
    mouseAndTouchNormalize: function(evt, svg) {
      // If no clientX then fallback
      if (evt.clientX === void 0 || evt.clientX === null) {
        // Fallback
        evt.clientX = 0;
        evt.clientY = 0;
  
        // If it is a touch event
        if (evt.touches !== void 0 && evt.touches.length) {
          if (evt.touches[0].clientX !== void 0) {
            evt.clientX = evt.touches[0].clientX;
            evt.clientY = evt.touches[0].clientY;
          } else if (evt.touches[0].pageX !== void 0) {
            var rect = svg.getBoundingClientRect();
  
            evt.clientX = evt.touches[0].pageX - rect.left;
            evt.clientY = evt.touches[0].pageY - rect.top;
          }
          // If it is a custom event
        } else if (evt.originalEvent !== void 0) {
          if (evt.originalEvent.clientX !== void 0) {
            evt.clientX = evt.originalEvent.clientX;
            evt.clientY = evt.originalEvent.clientY;
          }
        }
      }
    },
  
    /**
     * Check if an event is a double click/tap
     * TODO: For touch gestures use a library (hammer.js) that takes in account other events
     * (touchmove and touchend). It should take in account tap duration and traveled distance
     *
     * @param  {Event}  evt
     * @param  {Event}  prevEvt Previous Event
     * @return {Boolean}
     */
    isDblClick: function(evt, prevEvt) {
      // Double click detected by browser
      if (evt.detail === 2) {
        return true;
      }
      // Try to compare events
      else if (prevEvt !== void 0 && prevEvt !== null) {
        var timeStampDiff = evt.timeStamp - prevEvt.timeStamp, // should be lower than 250 ms
          touchesDistance = Math.sqrt(
            Math.pow(evt.clientX - prevEvt.clientX, 2) +
              Math.pow(evt.clientY - prevEvt.clientY, 2)
          );
  
        return timeStampDiff < 250 && touchesDistance < 10;
      }
  
      // Nothing found
      return false;
    },
  
    /**
     * Returns current timestamp as an integer
     *
     * @return {Number}
     */
    now:
      Date.now ||
      function() {
        return new Date().getTime();
      },
  
    // From underscore.
    // Returns a function, that, when invoked, will only be triggered at most once
    // during a given window of time. Normally, the throttled function will run
    // as much as it can, without ever going more than once per `wait` duration;
    // but if you'd like to disable the execution on the leading edge, pass
    // `{leading: false}`. To disable execution on the trailing edge, ditto.
    throttle: function(func, wait, options) {
      var that = this;
      var context, args, result;
      var timeout = null;
      var previous = 0;
      if (!options) {
        options = {};
      }
      var later = function() {
        previous = options.leading === false ? 0 : that.now();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) {
          context = args = null;
        }
      };
      return function() {
        var now = that.now();
        if (!previous && options.leading === false) {
          previous = now;
        }
        var remaining = wait - (now - previous);
        context = this; // eslint-disable-line consistent-this
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
          clearTimeout(timeout);
          timeout = null;
          previous = now;
          result = func.apply(context, args);
          if (!timeout) {
            context = args = null;
          }
        } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaining);
        }
        return result;
      };
    },
  
    /**
     * Create a requestAnimationFrame simulation
     *
     * @param  {Number|String} refreshRate
     * @return {Function}
     */
    createRequestAnimationFrame: function(refreshRate) {
      var timeout = null;
  
      // Convert refreshRate to timeout
      if (refreshRate !== "auto" && refreshRate < 60 && refreshRate > 1) {
        timeout = Math.floor(1000 / refreshRate);
      }
  
      if (timeout === null) {
        return window.requestAnimationFrame || requestTimeout(33);
      } else {
        return requestTimeout(timeout);
      }
    }
  };
  
  /**
   * Create a callback that will execute after a given timeout
   *
   * @param  {Function} timeout
   * @return {Function}
   */
  function requestTimeout(timeout) {
    return function(callback) {
      window.setTimeout(callback, timeout);
    };
  }
  
  },{}]},{},[3]);
  