|
|
@@ -0,0 +1,2242 @@
|
|
|
+// svg-pan-zoom v3.6.1
|
|
|
+// https://github.com/ariutta/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]);
|