Skip to content

Commit

Permalink
Separate Zoom for X-Y axis bumbu#176
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyVignelles committed Jan 3, 2016
1 parent d48cdbc commit 60eb591
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 74 deletions.
37 changes: 37 additions & 0 deletions demo/separate-zoom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>

<head>
<script src="../dist/svg-pan-zoom.js"></script>
</head>

<body>
<h1>Demo for svg-pan-zoom: timeline with X-only zoom</h1>
<div id="container" style="width: 500px; height: 100px; border:1px solid black;">
<svg id="demo" xmlns="http://www.w3.org/2000/svg" width="500" height="100">
<rect x="10" y="9" width="100" height="2" fill="blue"></rect>
<rect x="50" y="19" width="200" height="2" fill="red"></rect>
<rect x="0" y="29" width="300" height="2" fill="green"></rect>
<rect x="250" y="39" width="10" height="2" fill="#888"></rect>
<rect x="265" y="49" width="185" height="2" fill="purple"></rect>
<rect x="300" y="59" width="50" height="2" fill="blue"></rect>
<rect x="310" y="69" width="100" height="2" fill="red"></rect>
<rect x="325" y="79" width="175" height="2" fill="green"></rect>
<rect x="400" y="89" width="50" height="2" fill="#888"></rect>
</svg>
</div>
<script type="text/javascript">
var zoomDemo = svgPanZoom('#demo', {
center:false,
fit: false,
separateZoomsEnabled: true,
beforeZoom: function(zooms) {
return {x: true, y: false};
},
beforePan: function() {
return {x: true, y: false};
}
});
</script>
</body>
</html>
182 changes: 136 additions & 46 deletions src/shadow-viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ ShadowViewport.prototype.init = function(viewport, options) {
this.options = options

// State cache
this.originalState = {zoom: 1, x: 0, y: 0}
this.activeState = {zoom: 1, x: 0, y: 0}
this.originalState = {zoomX: 1, zoomY: 1, x: 0, y: 0}
this.activeState = {zoomX: 1, zoomY: 1, x: 0, y: 0}

this.updateCTMCached = Utils.proxy(this.updateCTM, this)

Expand Down Expand Up @@ -53,12 +53,21 @@ ShadowViewport.prototype.cacheViewBox = function() {
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)
var zoomX;
var zoomY;
if(this.options.separateZoomsEnabled) {
zoomX = this.options.width / this.viewBox.width;

This comment has been minimized.

Copy link
@bumbu

bumbu Jan 10, 2016

@jeremyVignelles by doing this the SVG contents will fill whole visible area on X and Y axes while default SVG behavior is to fit viewBox coordinates into current viewport. Thus I would keep only line 62

zoomY = this.options.height / this.viewBox.height;
} else {
zoomX = zoomY = 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
this.activeState.zoomX = zoomX;
this.activeState.zoomY = zoomY;
this.activeState.x = (this.options.width - this.viewBox.width * zoomX) / 2
this.activeState.y = (this.options.height - this.viewBox.height * zoomY) / 2

// Force updating CTM
this.updateCTMOnNextFrame()
Expand All @@ -80,8 +89,8 @@ ShadowViewport.prototype.cacheViewBox = function() {
*/
ShadowViewport.prototype.recacheViewBox = function() {
var boundingClientRect = this.viewport.getBoundingClientRect()
, viewBoxWidth = boundingClientRect.width / this.getZoom()
, viewBoxHeight = boundingClientRect.height / this.getZoom()
, viewBoxWidth = boundingClientRect.width / this.getZooms().x
, viewBoxHeight = boundingClientRect.height / this.getZooms().y

// Cache viewbox
this.viewBox.x = 0
Expand All @@ -107,31 +116,37 @@ ShadowViewport.prototype.processCTM = function() {
var newCTM = this.getCTM()

if (this.options.fit || this.options.contain) {
var newScale;
var newScaleX;
var newScaleY;

var scaleX = this.options.width/this.viewBox.width;
var scaleY = this.options.height/this.viewBox.height;

if (this.options.fit) {
newScale = Math.min(this.options.width/this.viewBox.width, this.options.height/this.viewBox.height);
newScaleX = newScaleY = Math.min(scaleX, scaleY);
} else {
newScale = Math.max(this.options.width/this.viewBox.width, this.options.height/this.viewBox.height);
newScaleX = newScaleY = Math.max(scaleX, scaleY);
}

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
newCTM.a = newScaleX; //x-scale
newCTM.d = newScaleY; //y-scale
newCTM.e = -this.viewBox.x * newScaleX; //x-transform
newCTM.f = -this.viewBox.y * newScaleY; //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
, offsetY = (this.options.height - (this.viewBox.height + this.viewBox.y * 2) * newCTM.d) * 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
this.originalState.zoomX = newCTM.a;
this.originalState.zoomY = newCTM.d;
this.originalState.x = newCTM.e;
this.originalState.y = newCTM.f;

// Update viewport CTM and cache zoom and pan
this.setCTM(newCTM);
Expand All @@ -156,30 +171,66 @@ ShadowViewport.prototype.getState = function() {
}

/**
* Get zoom scale
*
* Get zoom scale.
* Use getZooms() instead to get separate x/y scales
* @return {Float} zoom scale
*/
ShadowViewport.prototype.getZoom = function() {
return this.activeState.zoom
if(this.options.separateZoomsEnabled) {
console.warn('You should use getZooms() instead of getZoom() if the option separateZoomsEnabled is set.');
}

return this.activeState.zoomX;
}

/**
* Get zoom scale for pubilc usage
*
* Get zoom scales.
* @return {Object}
*/
ShadowViewport.prototype.getZooms = function() {
return {x: this.activeState.zoomX, y: this.activeState.zoomY };
}

/**
* Get zoom scale for public usage
* Use getRelativeZooms() instead to get separate x/y scales
* @return {Float} zoom scale
*/
ShadowViewport.prototype.getRelativeZoom = function() {
return this.activeState.zoom / this.originalState.zoom
if(this.options.separateZoomsEnabled) {
console.warn('You should use getRelativeZooms() instead of getRelativeZoom() if the option separateZoomsEnabled is set.');
}

return this.activeState.zoomX / this.originalState.zoomX
}

/**
* Compute zoom scale for pubilc usage
* Get zoom scales for public usage
* @return {Object} zoom scales for x and y
*/
ShadowViewport.prototype.getRelativeZooms = function() {
return {x:this.activeState.zoomX / this.originalState.zoomX, y:this.activeState.zoomY / this.originalState.zoomY}
}

/**
* Compute zoom scale for public usage
*
* @return {Float} zoom scale
*/
ShadowViewport.prototype.computeRelativeZoom = function(scale) {
return scale / this.originalState.zoom
if(this.options.separateZoomsEnabled) {
console.warn('You should use computeRelativeZooms() instead of computeRelativeZoom() if the option separateZoomsEnabled is set.');
}

return scale / this.originalState.zoomX
}

/**
* Compute zoom scales for public usage
* @return {Object} zoom scales for x and y
*/
ShadowViewport.prototype.computeRelativeZooms = function(scaleX, scaleY) {
return {x:scaleX / this.originalState.zoomX, y:scaleY / this.originalState.zoomY };
}

/**
Expand All @@ -199,11 +250,11 @@ ShadowViewport.prototype.getPan = function() {
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
// Copy values manually as in FF they are not iterable
safeCTM.a = this.activeState.zoomX
safeCTM.b = 0
safeCTM.c = 0
safeCTM.d = this.activeState.zoom
safeCTM.d = this.activeState.zoomY
safeCTM.e = this.activeState.x
safeCTM.f = this.activeState.y

Expand All @@ -222,46 +273,78 @@ ShadowViewport.prototype.setCTM = function(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
if(this.options.separateZoomsEnabled) {
var canZoom = this.options.beforeZoom(this.getRelativeZooms(), this.computeRelativeZooms(newCTM.a, newCTM.d));
var preventZoomX = false,
preventZoomY = false;
if(canZoom === false) {
newCTM.a = this.getZooms().x;
newCTM.d = this.getZooms().y;

preventZoomX = preventPanY = true;
} else if (Utils.isObject(canZoom)) {
if(canZoom.x === false) {
// Prevent horizontal scaling
newCTM.a = this.getZooms().x;
preventZoomX = true;
} else if(Utils.isNumber(canZoom.x)) {
newCTM.a = canZoom.x * this.originalState.zoomX;
}

if(canZoom.y === false) {
// Prevent vertical scaling
newCTM.d = this.getZooms().y;
preventZoomY = true;
} else if(Utils.isNumber(canZoom.y)) {
newCTM.d = canZoom.y * this.originalState.zoomY;
}
}

if(preventZoomX && preventZoomY) {
willZoom = false
}
} else {
// 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
}
}
}

// Before pan
if (willPan) {
var preventPan = this.options.beforePan(this.getPan(), {x: newCTM.e, y: newCTM.f})
var canPan = 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) {
if (canPan === 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)) {
} else if (Utils.isObject(canPan)) {
// Check for X axes attribute
if (preventPan.x === false) {
if (canPan.x === false) {
// Prevent panning on x axes
newCTM.e = this.getPan().x
preventPanX = true
} else if (Utils.isNumber(preventPan.x)) {
} else if (Utils.isNumber(canPan.x)) {
// Set a custom pan value
newCTM.e = preventPan.x
newCTM.e = canPan.x
}

// Check for Y axes attribute
if (preventPan.y === false) {
if (canPan.y === false) {
// Prevent panning on x axes
newCTM.f = this.getPan().y
preventPanY = true
} else if (Utils.isNumber(preventPan.y)) {
} else if (Utils.isNumber(canPan.y)) {
// Set a custom pan value
newCTM.f = preventPan.y
newCTM.f = canPan.y
}
}

Expand All @@ -278,14 +361,20 @@ ShadowViewport.prototype.setCTM = function(newCTM) {
this.updateCTMOnNextFrame()

// After callbacks
if (willZoom) {this.options.onZoom(this.getRelativeZoom())}
if (willZoom) {
if(this.options.separateZoomsEnabled) {
this.options.onZoom(this.getRelativeZooms())
} else {
this.options.onZoom(this.getRelativeZoom())
}
}
if (willPan) {this.options.onPan(this.getPan())}
}
}
}

ShadowViewport.prototype.isZoomDifferent = function(newCTM) {
return this.activeState.zoom !== newCTM.a
return this.activeState.zoomX !== newCTM.a || this.activeState.zoomY !== newCTM.d
}

ShadowViewport.prototype.isPanDifferent = function(newCTM) {
Expand All @@ -299,7 +388,8 @@ ShadowViewport.prototype.isPanDifferent = function(newCTM) {
* @param {SVGMatrix} newCTM
*/
ShadowViewport.prototype.updateCache = function(newCTM) {
this.activeState.zoom = newCTM.a
this.activeState.zoomX = newCTM.a
this.activeState.zoomY = newCTM.d
this.activeState.x = newCTM.e
this.activeState.y = newCTM.f
}
Expand Down

0 comments on commit 60eb591

Please sign in to comment.