How to set transform origin in SVG

SvgCoordinate Transformation

Svg Problem Overview


I need to resize and rotate certain elements in SVG document using javascript. The problem is, by default, it always applies the transform around the origin at (0, 0) – top left.

How can I re-define this transform anchor point?

I tried using the transform-origin attribute, but it does not affect anything.

This is how I did it:

svg.getDocumentById('someId').setAttribute('transform-origin', '75 240');

It does not seem to set the pivotal point to the point I specified although I can see in Firefox that the attribute is correctly set. I tried things like center bottom and 50% 100% with and without parenthesis. Nothing worked so far.

Can anyone help?

Svg Solutions


Solution 1 - Svg

To rotate use transform="rotate(deg, cx, cy)", where deg is the degree you want to rotate and (cx, cy) define the centre of rotation.

For scaling/resizing, you have to translate by (-cx, -cy), then scale and then translate back to (cx, cy). You can do this with a matrix transform:

transform="matrix(sx, 0, 0, sy, cx-sx*cx, cy-sy*cy)"

Where sx is the scaling factor in the x-axis, sy in the y-axis.

Solution 2 - Svg

svg * { 
  transform-box: fill-box;
}

applying transform-box: fill-box will make an element within an SVG behave as a normal HTML element. Then you can apply transform-origin: center (or something else) as you would normally

that's right, transform-box: fill-box. These days, there's no need for any complicated matrix stuff

Solution 3 - Svg

If you can use a fixed value (not "center" or "50%"), you can use CSS instead:

-moz-transform-origin: 25px 25px;
-ms-transform-origin:  25px 25px;
-o-transform-origin: 25px 25px;
-webkit-transform-origin:  25px 25px;
transform-origin: 25px 25px;

Some browsers (like Firefox) won't handle relative values correctly.

Solution 4 - Svg

If you're like me and want to pan and then zoom with transform-origin, you'll need a little more.

// <g id="view"></g>
var view = document.getElementById("view");

var state = {
  x: 0,
  y: 0,
  scale: 1
};

// Origin of transform, set to mouse position or pinch center
var oX = window.innerWidth/2;
var oY = window.innerHeight/2;

var changeScale = function (scale) {
  // Limit the scale here if you want
  // Zoom and pan transform-origin equivalent
  var scaleD = scale / state.scale;
  var currentX = state.x;
  var currentY = state.y;
  // The magic
  var x = scaleD * (currentX - oX) + oX;
  var y = scaleD * (currentY - oY) + oY;

  state.scale = scale;
  state.x = x;
  state.y = y;

  var transform = "matrix("+scale+",0,0,"+scale+","+x+","+y+")";
  //var transform = "translate("+x+","+y+") scale("+scale+")"; //same
  view.setAttributeNS(null, "transform", transform);
};

Here it is working: http://forresto.github.io/dataflow-prototyping/react/

Solution 5 - Svg

For scaling without having to use the matrix transformation:

transform="translate(cx, cy) scale(sx sy) translate(-cx, -cy)"

And here it is in CSS:

transform: translate(cxpx, cypx) scale(sx, sy) translate(-cxpx, -cypx)

Solution 6 - Svg

You can build whatever you want around 0,0 origin, then put it into a group <g></g> and matrix-translate the whole group. Like that, the object will always rotate around 0,0 , but the whole group is moved (translated) elsewhere and translation matrix is applied after the rotation.

<g transform="matrix(1 0 0 1 160 200)">
  <polygon id="arrow-A" class="arrow" points="0,4 -90,0 0,-4 "/>
</g>


<style>

.arrow {
  transform: rotate(60deg);
}

/* OR */

#arrow-A {
  transform: rotate(60deg);
}

</style>

OR scripting: 

<script> 
  document.getElementById("arrow-A").setAttribute("transform", "rotate(60)");
</script>

This will create an arrow (eg. for a gauge), the broader end at [0,0] and move it to [160, 200]. Whichever rotation is applied to class "arrow", will rotate it around [160, 200].

Tested in: Chrome, Firefox, Opera, MS Edge

Solution 7 - Svg

I had a similar issue. But I was using D3 to position my elements, and wanted the transform and transition to be handled by CSS. This was my original code, which I got working in Chrome 65:

//...
this.layerGroups.selectAll('.dot')
  .data(data)
  .enter()
  .append('circle')
  .attr('transform-origin', (d,i)=> `${valueScale(d.value) * Math.sin( sliceSize * i)} 
                                     ${valueScale(d.value) * Math.cos( sliceSize * i + Math.PI)}`)
//... etc (set the cx, cy and r below) ...

This allowed me to set the cx,cy, and transform-origin values in javascript using the same data.

BUT this didn't work in Firefox! What I had to do was wrap the circle in the g tag and translate it using the same positioning formula from above. I then appended the circle in the g tag, and set its cx and cy values to 0. From there, transform: scale(2) would scale from the center as expected. The final code looked like this.

this.layerGroups.selectAll('.dot')
  .data(data)
  .enter()
  .append('g')
  .attrs({
    class: d => `dot ${d.metric}`,
    transform: (d,i) => `translate(${valueScale(d.value) * Math.sin( sliceSize * i)} ${valueScale(d.value) * Math.cos( sliceSize * i + Math.PI)})`
  })
  .append('circle')
  .attrs({
    r: this.opts.dotRadius,
    cx: 0,
    cy: 0,
  })

After making this change, I changed my CSS to target the circle instead of the .dot, to add the transform: scale(2). I didn't even need use transform-origin.

NOTES:

  1. I am using d3-selection-multi in the second example. This allows me to pass an object to .attrs instead of repeating .attr for every attribute.

  2. When using a string template literal, be aware of line-breaks as illustrated in the first example. This will include a newline in the output and may break your code.

Solution 8 - Svg

Setting the attribute (transform-origin="center") of the embedded element just inside the DOM did the trick for me

          <circle
          fill="#FFFFFF"
          cx="82"
          cy="81.625"
          r="81.5"
          transform-origin="center"
        ></circle>

Works well with using css transforms

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCTheDarkView Question on Stackoverflow
Solution 1 - SvgPeter CollingridgeView Answer on Stackoverflow
Solution 2 - SvgGeorgeView Answer on Stackoverflow
Solution 3 - SvgHafenkranichView Answer on Stackoverflow
Solution 4 - SvgforrestoView Answer on Stackoverflow
Solution 5 - SvgcmititiucView Answer on Stackoverflow
Solution 6 - SvgPaalView Answer on Stackoverflow
Solution 7 - SvgJamie SView Answer on Stackoverflow
Solution 8 - SvgunxView Answer on Stackoverflow