Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support flipped svgs #146

Closed
charleslaw opened this issue Jun 17, 2018 · 2 comments
Closed

Support flipped svgs #146

charleslaw opened this issue Jun 17, 2018 · 2 comments

Comments

@charleslaw
Copy link

I am using this lib for a web page that is drawing SVGs that can have flipped x or y coordinates (the g transform attr is set to scale(1, -1) for example).

I was able to in a not terrible way, add support for this in a local copy of the non minified code - the diff is below. Basically, k represents scale always, but a kxy param maps k to the scaling in x and y. The change means that any new Transform has to carry the kxy param, and the toString() function needs to take kxy into account, the rest basically just works. I wanted to share but am not sure what is needed to make this into a mergeable change - does this look like something that can be mergeable one day?

diff --git a/d3-zoom.v1.js b/d3-zoom.v1.js
index eb97b7d..6128cba 100644
--- a/d3-zoom.v1.js
+++ b/d3-zoom.v1.js
@@ -17,19 +17,21 @@ function ZoomEvent(target, type, transform) {
   this.transform = transform;
 }
 
-function Transform(k, x, y) {
+function Transform(k, x, y, kxy) {
   this.k = k;
   this.x = x;
   this.y = y;
+  //This can be undfined, it's handled later
+  this.kxy = kxy;
 }
 
 Transform.prototype = {
   constructor: Transform,
   scale: function(k) {
-    return k === 1 ? this : new Transform(this.k * k, this.x, this.y);
+    return k === 1 ? this : new Transform(this.k * k, this.x, this.y, this.kxy);
   },
   translate: function(x, y) {
-    return x === 0 & y === 0 ? this : new Transform(this.k, this.x + this.k * x, this.y + this.k * y);
+    return x === 0 & y === 0 ? this : new Transform(this.k, this.x + this.k * x, this.y + this.k * y, this.kxy);
   },
   apply: function(point) {
     return [point[0] * this.k + this.x, point[1] * this.k + this.y];
@@ -56,12 +58,51 @@ Transform.prototype = {
     return y.copy().domain(y.range().map(this.invertY, this).map(y.invert, y));
   },
   toString: function() {
-    return "translate(" + this.x + "," + this.y + ") scale(" + this.k + ")";
+    var scale = "scale(";
+    if (this.kxy) {
+        //TODO: Matrix?
+        scale += this.kxy[0]*this.k+","+this.kxy[1]*this.k;
+    } else {
+        scale += this.k;
+    }
+    scale += ")";
+    return "translate(" + this.x + "," + this.y + ") " + scale;
   }
 };
 
 var identity = new Transform(1, 0, 0);
 
+var parse = function(a){
+    //https://stackoverflow.com/a/17838403/557406
+    var b = {};
+    for (var i in a = a.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?)+\))+/g)){
+        var c = a[i].match(/[\w\.\-]+/g);
+        b[c.shift()] = c.map(parseFloat);
+    }
+    return b;
+}
+
+var parseTransform = function (transformString){
+    if (!transformString) {
+        return identity;
+    }
+    var params = parse(transformString);
+    if (!params.translate) {
+        params.translate = [0,0];
+    }
+    if (!params.scale) {
+        params.scale = [1];
+    }
+    var k = Math.abs(params.scale[0]);
+    //NOTE: Parse will not have attr empty strings, instead it's just undefined
+    //We only use a pure k if scale is not flipped in x or y
+    if (k === params.scale[0] && (params.scale.length <2 || params.scale[1] === k)){
+        return new Transform(k, params.translate[0], params.translate[1]);
+    }
+    var kxy = [params.scale[0] / k, params.scale[1] / k];
+    return new Transform(k, params.translate[0], params.translate[1], kxy);
+}
+
 transform.prototype = Transform.prototype;
 
 function transform(node) {
@@ -136,9 +177,9 @@ var zoom = function() {
       wheelDelay = 150,
       clickDistance2 = 0;
 
-  function zoom(selection) {
+  function zoom(selection, parsedTransform) {
     selection
-        .property("__zoom", defaultTransform)
+        .property("__zoom", parsedTransform || defaultTransform)
         .on("wheel.zoom", wheeled)
         .on("mousedown.zoom", mousedowned)
         .on("dblclick.zoom", dblclicked)
@@ -207,12 +248,12 @@ var zoom = function() {
 
   function scale(transform$$1, k) {
     k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], k));
-    return k === transform$$1.k ? transform$$1 : new Transform(k, transform$$1.x, transform$$1.y);
+    return k === transform$$1.k ? transform$$1 : new Transform(k, transform$$1.x, transform$$1.y, transform$$1.kxy);
   }
 
   function translate(transform$$1, p0, p1) {
     var x = p0[0] - p1[0] * transform$$1.k, y = p0[1] - p1[1] * transform$$1.k;
-    return x === transform$$1.x && y === transform$$1.y ? transform$$1 : new Transform(transform$$1.k, x, y);
+    return x === transform$$1.x && y === transform$$1.y ? transform$$1 : new Transform(transform$$1.k, x, y, transform$$1.kxy);
   }
 
   function centroid(extent) {
@@ -235,7 +276,7 @@ var zoom = function() {
               i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k));
           return function(t) {
             if (t === 1) t = b; // Avoid rounding error on end.
-            else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k); }
+            else { var l = i(t), k = w / l[2]; t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k, a.kxy); }
             g.zoom(null, t);
           };
         });
@@ -283,15 +324,17 @@ var zoom = function() {
       return this;
     },
     emit: function(type) {
+      //TODO: Fix array if both elements are the same
       d3Selection.customEvent(new ZoomEvent(zoom, type, this.that.__zoom), listeners.apply, listeners, [type, this.that, this.args]);
     }
   };
 
   function wheeled() {
     if (!filter.apply(this, arguments)) return;
+    //Make scale an array for everything here
     var g = gesture(this, arguments),
         t = this.__zoom,
-        k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))),
+        k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k  * Math.pow(2, wheelDelta.apply(this, arguments)))),
         p = d3Selection.mouse(this);
 
     // If the mouse is in the same location as before, reuse it.
@@ -303,7 +346,7 @@ var zoom = function() {
       clearTimeout(g.wheel);
     }
 
-    // If this wheel event won’t trigger a transform change, ignore it.
+    // If this wheel event won't trigger a transform change, ignore it.
     else if (t.k === k) return;
 
     // Otherwise, capture the mouse point and location at the start.
@@ -496,6 +539,7 @@ var zoom = function() {
 exports.zoom = zoom;
 exports.zoomTransform = transform;
 exports.zoomIdentity = identity;
+exports.parseTransform = parseTransform;
 
 Object.defineProperty(exports, '__esModule', { value: true });
 
@charleslaw
Copy link
Author

https://stackoverflow.com/a/38230545/557406 would be better for parsing the string, though this is svg specific too

@mbostock
Copy link
Member

It sounds like this would be supported by #48.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants