How to perform rotation in SVG path data (d) string itself?

Viewed 93

I'm making a graph and chart web app and I want to perform the rotation operation on different shapes in it. Not using CSS or any transform attribute, I want to apply rotation on the path data (d parameter) itself. So, how can I mathematically process the points in the "d" attribute (both capital and small latter commands) so that It output a new "d" string representing the rotated shape after theta degree rotation about the origin (x,y)? For example, in this path string, d="M349,228h212v-1331h-212v133", I want to apply transform=rotate(24) and transform-origin="455px 161.5px". What will be the final d after I apply these transformations?

2 Answers

To transform points in 2D space, use the following formulae, where the rotation direction is counter-clockwise and the angles are in radians.

rotatedX = x * cosθ - y * sinθ

rotatedY = x * sinθ + y * cosθ

Another approach might be to transform path coordiates via matrixTransform().

This concept is used in timo22345's gist flatten.js and can also convert other transformations such as scale() or translate() to hardcoded path commands.

Simplified example

/**
 * getTransformToElement polyfill
 */
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) {
  let matrix = toElement.getScreenCTM().inverse().multiply(this.getScreenCTM())
  return matrix;
};

function convertTransform(decimals = 3) {
  let svg = document.querySelector('svg');
  let transformed = svg.querySelectorAll('*[transform]');
  let transformedTotal = transformed.length;

  if (transformedTotal) {
    transformed.forEach(function(el, i) {
      let type = el.nodeName.toLowerCase();
      let shapes = ['path', 'circle', 'rect', 'polygon', 'polyline', 'line', 'ellipse'];

      if (shapes.indexOf(type) !== -1) {
        // normalize pathdata to get absolute coordinates
        let pathData = el.getPathData({
          normalize: true
        })

        // get transform matrix
        let matrix = el.getTransformToElement(el.parentNode);
        el.removeAttribute('transform');

        pathData.forEach(function(command, d) {
          let values = command.values;
          // loop through coordinates: 
          for (let v = 0; v < values.length - 1; v += 2) {
            let [x, y] = [values[v], values[v + 1]];
            let pt = svg.createSVGPoint();
            pt.x = x;
            pt.y = y;
            // change coordinates by matrix transform
            let pTrans = pt.matrixTransform(matrix);
            // save coordinates to pathdata array
            pathData[d]['values'][v] = +pTrans.x.toFixed(decimals);
            pathData[d]['values'][v + 1] = +pTrans.y.toFixed(decimals);
          }
        })

        //check if conversion is needed for primitives (rect, circle, polygons etc.)
        if (type !== 'path') {
          let newPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
          let atts = [...el.attributes];
          let excludedAtts = ['d', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'points', 'height', 'width'];
          for (let a = 0; a < atts.length; a++) {
            let att = atts[a];
            if (excludedAtts.indexOf(att.nodeName) === -1) {
              var attrName = att.nodeName;
              var attrValue = att.nodeValue;
              newPath.setAttribute(attrName, attrValue + '');
            }
          }
          el.replaceWith(newPath);
          el = newPath;
        }
        el.setPathData(pathData)
      }
    })
  }
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<p><button type="button" onclick="convertTransform()"> Convert transforms </button></p>
<svg viewBox="0 0 100 100" overflow="visible">
            <path class="" d="M25 25 h25 v25 h-25 z" fill="green" />
            <rect x="0" y="0" width="33%" height="20" transform="rotate(27 20 50) translate(20 10)" fill="orange" />    
            <path data-att="random" transform="rotate(45 50 50) scale(1.2) translate(10 -20)" class="transform" d="M25 25 h25 v25 h-25 z" fill="red" />
    </svg>

How it works

  1. we need to get each element's transform matrix via getTransformToElement() (polyfill included)
  2. we need to convert path data to absolute commands (using Jarek Foksa's path-data-polyfill)
  3. each command's point (x/y) can be transformed to new x/y values using point.matrixTransform(matrix)

The above script is simplified: it can't convert nested transformations (like nested transformed groups) – timo22345 flatten.js can do this trick by applying multiple conversions (and it doesn't need auxiliary libs/polyfills).

Example based on flatten.js (multiple nestes transformations)

let svg = document.querySelector('svg');


SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(e) {
    return e.getScreenCTM().inverse().multiply(this.getScreenCTM())
  },
  function() {
    var e = /,?([achlmqrstvxz]),?/gi,
      t = function(t) {
        return t.join(",").replace(e, "$1")
      };

    function r(e, t, r, a, n, u, i, c) {
      function o(e) {
        return Math.abs(e) < 1e-16
      }
      var l, s, h, f, x, y, p, g, b, v, m, M = [];
      if (l = e, s = t, h = r *= Math.PI / 180, f = parseFloat(Math.sin(h)), x = parseFloat(Math.cos(h)), M[0] = i.a * +l * x + i.c * l * f, M[1] = i.b * +l * x + i.d * l * f, M[2] = i.a * -s * f + i.c * s * x, M[3] = i.b * -s * f + i.d * s * x, b = (y = M[0] * M[0] + M[2] * M[2]) - (g = M[1] * M[1] + M[3] * M[3]), o(p = 2 * (M[0] * M[1] + M[2] * M[3]))) r = 0, v = y, m = g;
      else if (o(b)) v = y + .5 * p, m = y - .5 * p, r = Math.PI / 4;
      else {
        var d = 1 + p * p / (b * b);
        v = .5 * (y + g + (d = d < 0 ? 0 : Math.sqrt(d)) * b), m = .5 * (y + g - d * b), r = .5 * Math.atan2(p, b)
      }
      return v = v < 0 ? 0 : Math.sqrt(v), m = m < 0 ? 0 : Math.sqrt(m), b <= 0 ? (t = v, e = m) : (t = m, e = v), i.a * i.d - i.b * i.c < 0 && (n = n ? 0 : 1), u = u.matrixTransform(i), ["A", e, t, r = 180 * r / Math.PI, a, n, u.x, u.y]
    }
    var a = {},
      n = "hasOwnProperty",
      u = String,
      i = "array",
      c = {
        NaN: 1,
        Infinity: 1,
        "-Infinity": 1
      },
      o = u.prototype.toLowerCase,
      l = u.prototype.toUpperCase,
      s = Object.prototype.toString,
      h = Math,
      f = h.max,
      x = (h.min, h.abs, h.pow, h.PI),
      y = (h.round, parseFloat),
      p = (parseInt, e = /,?([achlmqrstvxz]),?/gi, /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi),
      g = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/gi;

    function b(e) {
      if (Object(e) !== e) return e;
      var t = new e.constructor;
      for (var r in e) e[n](r) && (t[r] = b(e[r]));
      return t
    }
    a.is = function(e, t) {
      return "finite" == (t = o.call(t)) ? !c[n](+e) : "array" == t ? e instanceof Array : "null" == t && null === e || t == typeof e && null !== e || "object" == t && e === Object(e) || "array" == t && Array.isArray && Array.isArray(e) || s.call(e).slice(8, -1).toLowerCase() == t
    }, a._path2string = function() {
      return this.join(",").replace(e, "$1")
    };
    var v = function(e) {
        var t = b(e);
        return t.toString = a._path2string, t
      },
      m = function(e) {
        var t = m.ps = m.ps || {};
        return t[e] ? t[e].sleep = 100 : t[e] = {
          sleep: 100
        }, setTimeout((function() {
          for (var r in t) t[n](r) && r != e && (t[r].sleep--, !t[r].sleep && delete t[r])
        })), t[e]
      },
      M = function(e) {
        if (!e) return null;
        var t = m(e);
        if (t.arr) return v(t.arr);
        var r = {
            a: 7,
            c: 6,
            h: 1,
            l: 2,
            m: 2,
            r: 4,
            q: 4,
            s: 4,
            t: 2,
            v: 1,
            z: 0
          },
          n = [];
        return a.is(e, i) && a.is(e[0], i) && (n = v(e)), n.length || u(e).replace(p, (function(e, t, a) {
          var u = [],
            i = t.toLowerCase();
          if (a.replace(g, (function(e, t) {
              t && u.push(+t)
            })), "m" == i && u.length > 2 && (n.push([t].concat(u.splice(0, 2))), i = "l", t = "m" == t ? "l" : "L"), "r" == i) n.push([t].concat(u));
          else
            for (; u.length >= r[i] && (n.push([t].concat(u.splice(0, r[i]))), r[i]););
        })), n.toString = a._path2string, t.arr = v(n), n
      };
    var d = A((function(e) {
      if (a.is(e, i) && a.is(e && e[0], i) || (e = M(e)), !e || !e.length) return [
        ["M", 0, 0]
      ];
      var t = [],
        r = 0,
        n = 0,
        u = 0,
        c = 0,
        o = 0;
      "M" == e[0][0] && (u = r = +e[0][1], c = n = +e[0][2], o++, t[0] = ["M", r, n]);
      for (var s, h, f = 3 == e.length && "M" == e[0][0] && "R" == e[1][0].toUpperCase() && "Z" == e[2][0].toUpperCase(), x = o, y = e.length; x < y; x++) {
        if (t.push(s = []), (h = e[x])[0] != l.call(h[0])) switch (s[0] = l.call(h[0]), s[0]) {
            case "A":
              s[1] = h[1], s[2] = h[2], s[3] = h[3], s[4] = h[4], s[5] = h[5], s[6] = +(h[6] + r), s[7] = +(h[7] + n);
              break;
            case "V":
              s[1] = +h[1] + n;
              break;
            case "H":
              s[1] = +h[1] + r;
              break;
            case "R":
              for (var p = [r, n].concat(h.slice(1)), g = 2, b = p.length; g < b; g++) p[g] = +p[g] + r, p[++g] = +p[g] + n;
              t.pop(), t = t.concat(catmullRom2bezier(p, f));
              break;
            case "M":
              u = +h[1] + r, c = +h[2] + n;
            default:
              for (g = 1, b = h.length; g < b; g++) s[g] = +h[g] + (g % 2 ? r : n)
          } else if ("R" == h[0]) p = [r, n].concat(h.slice(1)), t.pop(), t = t.concat(catmullRom2bezier(p, f)), s = ["R"].concat(h.slice(-2));
          else
            for (var v = 0, m = h.length; v < m; v++) s[v] = h[v];
        switch (s[0]) {
          case "Z":
            r = u, n = c;
            break;
          case "H":
            r = s[1];
            break;
          case "V":
            n = s[1];
            break;
          case "M":
            u = s[s.length - 2], c = s[s.length - 1];
          default:
            r = s[s.length - 2], n = s[s.length - 1]
        }
      }
      return t.toString = a._path2string, t
    }));

    function A(e, t, r) {
      return function a() {
        var n = Array.prototype.slice.call(arguments, 0),
          u = n.join("␀"),
          i = a.cache = a.cache || {},
          c = a.count = a.count || [];
        if (i.hasOwnProperty(u)) {
          for (var o = 0, l = c.length; o < l; o++) c[o] === u && c.push(c.splice(o, 1)[0]);
          return r ? r(i[u]) : i[u]
        }
        return c.length >= 1e3 && delete i[c.shift()], c.push(u), i[u] = e.apply(t, n), r ? r(i[u]) : i[u]
      }
    }
    var C = function(e, t, r, a) {
        return [e, t, r, a, r, a]
      },
      q = function(e, t, r, a, n, u) {
        var i = 1 / 3,
          c = 2 / 3;
        return [i * e + c * r, i * t + c * a, i * n + c * r, i * u + c * a, n, u]
      },
      S = A((function(e, t, r, a, n, u, i, c, o, l) {
        var s, h = 120 * x / 180,
          f = x / 180 * (+n || 0),
          y = [],
          p = A((function(e, t, r) {
            return {
              x: e * Math.cos(r) - t * Math.sin(r),
              y: e * Math.sin(r) + t * Math.cos(r)
            }
          }));
        if (l) w = l[0], V = l[1], C = l[2], q = l[3];
        else {
          e = (s = p(e, t, -f)).x, t = s.y, c = (s = p(c, o, -f)).x, o = s.y;
          Math.cos(x / 180 * n), Math.sin(x / 180 * n);
          var g = (e - c) / 2,
            b = (t - o) / 2,
            v = g * g / (r * r) + b * b / (a * a);
          v > 1 && (r *= v = Math.sqrt(v), a *= v);
          var m = r * r,
            M = a * a,
            d = (u == i ? -1 : 1) * Math.sqrt(Math.abs((m * M - m * b * b - M * g * g) / (m * b * b + M * g * g))),
            C = d * r * b / a + (e + c) / 2,
            q = d * -a * g / r + (t + o) / 2,
            w = Math.asin(((t - q) / a).toFixed(9)),
            V = Math.asin(((o - q) / a).toFixed(9));
          (w = e < C ? x - w : w) < 0 && (w = 2 * x + w), (V = c < C ? x - V : V) < 0 && (V = 2 * x + V), i && w > V && (w -= 2 * x), !i && V > w && (V -= 2 * x)
        }
        var k = V - w;
        if (Math.abs(k) > h) {
          var L = V,
            E = c,
            T = o;
          V = w + h * (i && V > w ? 1 : -1), c = C + r * Math.cos(V), o = q + a * Math.sin(V), y = S(c, o, r, a, n, 0, i, E, T, [V, L, C, q])
        }
        k = V - w;
        var G = Math.cos(w),
          P = Math.sin(w),
          Z = Math.cos(V),
          _ = Math.sin(V),
          j = Math.tan(k / 4),
          z = 4 / 3 * r * j,
          H = 4 / 3 * a * j,
          I = [e, t],
          R = [e + z * P, t - H * G],
          F = [c + z * _, o - H * Z],
          N = [c, o];
        if (R[0] = 2 * I[0] - R[0], R[1] = 2 * I[1] - R[1], l) return [R, F, N].concat(y);
        for (var O = [], U = 0, Q = (y = [R, F, N].concat(y).join().split(",")).length; U < Q; U++) O[U] = U % 2 ? p(y[U - 1], y[U], f).y : p(y[U], y[U + 1], f).x;
        return O
      })),
      w = A((function(e, t) {
        var r = !t && m(e);
        if (!t && r.curve) return v(r.curve);
        for (var a = d(e), n = t && d(t), u = {
            x: 0,
            y: 0,
            bx: 0,
            by: 0,
            X: 0,
            Y: 0,
            qx: null,
            qy: null
          }, i = {
            x: 0,
            y: 0,
            bx: 0,
            by: 0,
            X: 0,
            Y: 0,
            qx: null,
            qy: null
          }, c = function(e, t, r) {
            var a, n;
            if (!e) return ["C", t.x, t.y, t.x, t.y, t.x, t.y];
            switch (!(e[0] in {
              T: 1,
              Q: 1
            }) && (t.qx = t.qy = null), e[0]) {
              case "M":
                t.X = e[1], t.Y = e[2];
                break;
              case "A":
                e = ["C"].concat(S.apply(0, [t.x, t.y].concat(e.slice(1))));
                break;
              case "S":
                "C" == r || "S" == r ? (a = 2 * t.x - t.bx, n = 2 * t.y - t.by) : (a = t.x, n = t.y), e = ["C", a, n].concat(e.slice(1));
                break;
              case "T":
                "Q" == r || "T" == r ? (t.qx = 2 * t.x - t.qx, t.qy = 2 * t.y - t.qy) : (t.qx = t.x, t.qy = t.y), e = ["C"].concat(q(t.x, t.y, t.qx, t.qy, e[1], e[2]));
                break;
              case "Q":
                t.qx = e[1], t.qy = e[2], e = ["C"].concat(q(t.x, t.y, e[1], e[2], e[3], e[4]));
                break;
              case "L":
                e = ["C"].concat(C(t.x, t.y, e[1], e[2]));
                break;
              case "H":
                e = ["C"].concat(C(t.x, t.y, e[1], t.y));
                break;
              case "V":
                e = ["C"].concat(C(t.x, t.y, t.x, e[1]));
                break;
              case "Z":
                e = ["C"].concat(C(t.x, t.y, t.X, t.Y))
            }
            return e
          }, o = function(e, t) {
            if (e[t].length > 7) {
              e[t].shift();
              for (var r = e[t]; r.length;) s[t] = "A", n && (h[t] = "A"), e.splice(t++, 0, ["C"].concat(r.splice(0, 6)));
              e.splice(t, 1), b = f(a.length, n && n.length || 0)
            }
          }, l = function(e, t, r, u, i) {
            e && t && "M" == e[i][0] && "M" != t[i][0] && (t.splice(i, 0, ["M", u.x, u.y]), r.bx = 0, r.by = 0, r.x = e[i][1], r.y = e[i][2], b = f(a.length, n && n.length || 0))
          }, s = [], h = [], x = "", p = "", g = 0, b = f(a.length, n && n.length || 0); g < b; g++) {
          a[g] && (x = a[g][0]), "C" != x && (s[g] = x, g && (p = s[g - 1])), a[g] = c(a[g], u, p), "A" != s[g] && "C" == x && (s[g] = "C"), o(a, g), n && (n[g] && (x = n[g][0]), "C" != x && (h[g] = x, g && (p = h[g - 1])), n[g] = c(n[g], i, p), "A" != h[g] && "C" == x && (h[g] = "C"), o(n, g)), l(a, n, u, i, g), l(n, a, i, u, g);
          var M = a[g],
            A = n && n[g],
            w = M.length,
            V = n && A.length;
          u.x = M[w - 2], u.y = M[w - 1], u.bx = y(M[w - 4]) || u.x, u.by = y(M[w - 3]) || u.y, i.bx = n && (y(A[V - 4]) || i.x), i.by = n && (y(A[V - 3]) || i.y), i.x = n && A[V - 2], i.y = n && A[V - 1]
        }
        return n || (r.curve = v(a)), n ? [a, n] : a
      }), null, v);
    window.flatten = function e(a, n = !1, u = !1, i = !1, c = 3) {
      if (a)
        if (void 0 === i && (i = !1), void 0 === n && (n = !1), void 0 === u && (u = !1), void 0 === c && (c = !1), a && a.children && a.children.length) {
          for (var o = 0, l = a.children.length; o < l; o++) e(a.children[o], n, u, i, c);
          a.removeAttribute("transform")
        } else if (a instanceof SVGCircleElement || a instanceof SVGRectElement || a instanceof SVGEllipseElement || a instanceof SVGLineElement || a instanceof SVGPolygonElement || a instanceof SVGPolylineElement || a instanceof SVGPathElement) {
        if (path_elem = function(e, r) {
            if (!e) return;
            var a = document.createElementNS(e.ownerSVGElement.namespaceURI, "path");
            let n = [...e.attributes],
              u = ["d", "x", "y", "x1", "y1", "x2", "y2", "cx", "cy", "r", "rx", "ry", "points", "height", "width"];
            for (let e = 0; e < n.length; e++) {
              let t = n[e];
              if (-1 === u.indexOf(t.nodeName)) {
                var i = t.nodeName,
                  c = t.nodeValue;
                a.setAttribute(i, c + "")
              }
            }
            var o = "",
              l = function(e) {
                return !("number" != typeof e || e == 1 / 0 || e < 0)
              },
              s = 1.81,
              h = e.tagName;
            switch (h) {
              case "ellipse":
              case "circle":
                var f = +e.getAttribute("rx"),
                  x = +e.getAttribute("ry"),
                  y = +e.getAttribute("cx"),
                  p = +e.getAttribute("cy");
                "circle" == h && (f = x = +e.getAttribute("r")), o += t([
                  ["M", y - f, p],
                  ["C", y - f, p - x / s, y - f / s, p - x, y, p - x],
                  ["C", y + f / s, p - x, y + f, p - x / s, y + f, p],
                  ["C", y + f, p + x / s, y + f / s, p + x, y, p + x],
                  ["C", y - f / s, p + x, y - f, p + x / s, y - f, p],
                  ["Z"]
                ]);
                break;
              case "path":
                o = e.getAttribute("d");
                break;
              case "line":
                var g = e.getAttribute("x1"),
                  b = e.getAttribute("y1");
                x2 = e.getAttribute("x2"), y2 = e.getAttribute("y2"), o = "M" + g + "," + b + "L" + x2 + "," + y2;
                break;
              case "polyline":
                o = "M" + e.getAttribute("points");
                break;
              case "polygon":
                o = "M" + e.getAttribute("points") + "Z";
                break;
              case "rect":
                f = +e.getAttribute("rx"), x = +e.getAttribute("ry");
                var v = e.getBBox(),
                  m = v.x,
                  M = v.y,
                  d = v.width,
                  A = v.height;
                if (l(f) || l(x) ? l(f) && !l(x) ? x = f : l(x) && !l(f) ? f = x : (f > d / 2 && (f = d / 2), x > A / 2 && (x = A / 2)) : f = x = 0, f || x)
                  if (r) o += t([
                    ["M", m + f, M],
                    ["H", m + d - f],
                    ["A", f, x, 0, 0, 1, m + d, M + x],
                    ["V", M + A - x],
                    ["A", f, x, 0, 0, 1, m + d - f, M + A],
                    ["H", m + f],
                    ["A", f, x, 0, 0, 1, m, M + A - x],
                    ["V", M + x],
                    ["A", f, x, 0, 0, 1, m + f, M]
                  ]);
                  else {
                    x || (x = f), o += t([
                      ["M", m, M + x],
                      ["C", m, M + x / (s = 2.19), m + f / s, M, m + f, M],
                      ["L", m + d - f, M],
                      ["C", m + d - f / s, M, m + d, M + x / s, m + d, M + x],
                      ["L", m + d, M + A - x],
                      ["C", m + d, M + A - x / s, m + d - f / s, M + A, m + d - f, M + A],
                      ["L", m + f, M + A],
                      ["C", m + f / s, M + A, m, M + A - x / s, m, M + A - x],
                      ["L", m, M + x],
                      ["Z"]
                    ])
                  }
                else o += t([
                  ["M", m, M],
                  ["L", m + d, M],
                  ["L", m + d, M + A],
                  ["L", m, M + A],
                  ["L", m, M],
                  ["Z"]
                ])
            }
            o && a.setAttribute("d", o);
            return e.parentNode.replaceChild(a, e), a
          }(a, i), !path_elem || "" == path_elem.getAttribute(f)) return "M 0 0";
        var s;
        c || 0 === c ? c > 15 ? c = 15 : c < 0 && (c = 0) : c = !1;
        var h = path_elem,
          f = h.getAttribute("d").trim();
        if (n) x = s = w(f);
        else {
          var x = s = M(f);
          s = d(s)
        }
        var y, p, g = h.ownerSVGElement,
          b = h.getTransformToElement(g),
          v = (o = 0, s.length),
          m = "",
          A = "",
          C = 0,
          q = 0,
          S = [],
          V = g.createSVGPoint(),
          k = {},
          L = 0,
          E = 0;
        for (k.x = null, k.y = null; o < v; o++) {
          if (m = s[o][0].toUpperCase(), A = x[o][0], S[o] = [], S[o][0] = s[o][0], "A" == m) C = s[o][6], q = s[o][7], V.x = s[o][6], V.y = s[o][7], S[o] = r(s[o][1], s[o][2], s[o][3], s[o][4], s[o][5], V, b), S[o][1] = S[o][1], S[o][2] = S[o][2], S[o][3] = S[o][3], S[o][6] = S[o][6], S[o][7] = S[o][7];
          else if ("Z" != m)
            for (y = 1; y < s[o].length; y += 2) "V" == m ? q = s[o][y] : "H" == m ? C = s[o][y] : (C = s[o][y], q = s[o][y + 1]), V.x = C, V.y = q, p = V.matrixTransform(b), "V" == m || "H" == m ? (S[o][0] = "L", S[o][y] = p.x, S[o][y + 1] = p.y) : (S[o][y] = p.x, S[o][y + 1] = p.y);
          ("Z" != m && null === k.x || "M" == m) && (k.x = C, k.y = q), "Z" == m && (C = k.x, q = k.y)
        }
        var T = 0,
          G = 0;
        for (k.x = "", o = 0; o < S.length; o++) {
          if ("A" == (A = x[o][0]) || "M" == A || "L" == A || "C" == A || "S" == A || "Q" == A || "T" == A || "H" == A || "V" == A) {
            var P = Z = S[o].length;
            if ("A" == A) S[o][6] = _(S[o][6]), S[o][7] = _(S[o][7]);
            else
              for (P--; --P;) S[o][P] = _(S[o][P]);
            L = S[o][Z - 2], E = S[o][Z - 1]
          } else if ("a" == A) T = S[o][6], G = S[o][7], S[o][0] = A, S[o][6] = _(S[o][6] - L), S[o][7] = _(S[o][7] - E), L = T, E = G;
          else if ("m" == A || "l" == A || "c" == A || "s" == A || "q" == A || "t" == A || "h" == A || "v" == A) {
            var Z = S[o].length;
            for (T = S[o][Z - 2], G = S[o][Z - 1], y = 1; y < Z; y += 2) S[o][0] = "h" == A || "v" == A ? "l" : A, S[o][y] = _(S[o][y] - L), S[o][y + 1] = _(S[o][y + 1] - E);
            L = T, E = G
          }("z" != A.toLowerCase() && "" == k.x || "m" == A.toLowerCase()) && (k.x = L, k.y = E), "z" == A.toLowerCase() && (L = k.x, E = k.y)
        }
        S[0][1] = +S[0][1].toFixed(c), S[0][2] = +S[0][2].toFixed(c), u && (S = d(S)), path_elem.setAttribute("d", t(S)), path_elem.removeAttribute("transform")
      }

      function _(e) {
        return !1 !== c ? Math.round(e * Math.pow(10, c)) / Math.pow(10, c) : e
      }
    }
  }();
<p><button type="button" onclick="flatten(svg)"> Convert transforms flatten.js</button></p>


<svg viewBox="0 0 100 100" overflow="visible">
        <g transform="rotate(-33 50 50) translate(-10 -10) scale(1.5)">
        <g transform="rotate(-33 50 50) translate(-10 -10) scale(1.5)">
                <path class="" d="M25 25 h25 v25 h-25 z" fill="green" />
                <rect x="0" y="0" width="33%" height="20" transform="rotate(27 20 50) translate(20 10)" fill="orange" />    
                <path data-att="random" transform="rotate(45 50 50)" class="transform" d="M25 25 h25 v25 h-25 z" fill="red" />
            </g>
        </g>
    </svg>

Converting (flattening) transformations might be a bad idea

At least when your users are frequently changing transformations.
Using a canvas driven (with svg export capabilities) library like fabric.js might be the better solution.

The aforementioned path data manipulations are lossy due to coordinate/number rounding. Negligible if done only once (e.g after editing or before exporting the transformed graphic as svg file).

Converting (flattening) transformations can be useful if

  • you're struggling with svg files with far too many transformations (sometimes negating each other ... sometimes just completely nuts)
  • you need to optimize your svg for external applications or generators with limited svg feature support (e.g pdf generators or office applications)
Related