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?
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.
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
transform-box: fill-box;
you can make svg elementes respect transform-origin
in a sane manner / like you'd expect!
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.
element.setAttribute('transform-origin', '25px 25px')
?
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/
id
matches in the getElementById
and the documents' id="..."
attribute. It's hard to know for sure without seeing the code. If you don't want to post it as an SO question, you can try creating the simplest example using your attribute names until it works/you find the issue, then add the rest back in.
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)
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.
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
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:
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. 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.
svg:transform-origin.enabled
pref in the firefox's about:config
page. But... that didn't seem like an adequate solution. I also found a discrepancy between transform-origin: 50% 50%
and transform-origin: center center
.
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
transform-box: fill-box
. see this answer stackoverflow.com/a/62720107/4375751
transform-origin="center"
), not (transform-origin: center
)). Tested it in all modern browsers and it worked without using fill-box
Success story sharing