SVG Transformation - svg

I want to move every shape present on S.V.G 600px down, as my previous S.V.G's initial coordinates was at "bottom-left" but in my new S.V.G, it's at the "top-left" of S.V.G, so to display the shapes of previous S.V.G on new S.V.G I just used to add the Previous S.V.G height i.e. 600px into Y-translation and it's working fine with shape like:
<g id="shape3165-80" transform="translate(105.375,-208.875)"></g>
As the new points are (105.375, 391.125) which is correct as from bottom it's 208.875 above.
But in the case of rotated shapes, it's not showing on correct position as on rotation it's translation point had been changed, for example:
In case of:
<g id="shape3164-77" transform="translate(263.251,868.355) rotate(180)"></g>
so on adding 600px it's coordinates becomes (263.251, 1468.355) which display them out of S.V.G box.
So I want to find:
The initial translate point of the shape before the rotation of 180deg.
Then add 600px to it.
Then again rotate it with 180deg.
So my question is am I doing it in right manner? Would you help me to find out the solution or formula for the same?

Because the shape is rotated 180 degrees, you will need to subtract 600 from the Y coordinate instead of adding 600.
So the second example should be:
<g id="shape3164-77" transform="translate(263.251,268.355) rotate(180)"></g>
Alternatively, add your 600 before the other transforms:
<g id="shape3164-77" transform="translate(263.251,868.355) rotate(180) translate(0,600)"></g>
(Because of the way the transform attribute works, you can think of transforms being applied from right to left. So to have one applied first, it has to be put at the end).

Related

What does inkscape:transform-center-x/y means?

I am using Inkscape to make SVG image and a little confused about the "transform-center-x" attribute like below:
<circle
style="display:inline;fill:#0000ff;fill-opacity:1;stroke:#000000;stroke-width:1.13386"
id="beacon-21737"
cx="-121.04593"
cy="42.20393"
r="1.9999999"
inkscape:transform-center-x="-0.6614634"
inkscape:transform-center-y="-10.318751"
inkscape:label="beacon"
transform="rotate(-90)">
</circle>
It seems not equal to rotate(angle, x, y). Please help me understand the meaning of the "transform-center-x/y".
This is a property of the grafical interface. If you click twice on a grafical object, you can rotate or skew it around a center indicated by a cross:
The cross can be moved by dragging it. Its position is stored in the inkscape:transform-center attribute. The value is in coordinates relative to the center of the bounding box of the grafical object. This position will also be used for other transforms, for example when you use the Object -> Transformation... dialog.
The SVG namespace transform will not reflect that center. Inkscape has an internal optimization algorithm to express rotations and other transforms, so the grafical and the standardized center might not coincide.
As always, other renderers will simply ignore tags and attributes in the inkscape namespace.

SVG Gradients with ObjectBoundingBox units and gradientTransform with rotation

Trying to replicate an SVG gradient with canvas drawing in javascript i came across this use case:
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="760"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 380 380">
<defs>
<linearGradient id="three_stops_4" gradientTransform="rotate(90)">
<stop offset="0%" style="stop-color: #ffcc00;"/>
<stop offset="33.3%" style="stop-color: #cc6699; stop-opacity: 1"/>
<stop offset="100%" style="stop-color: #66cc99; stop-opacity: 1"/>
</linearGradient>
<polygon id="base" points="5,5 35,5 35,40 5,40 5,35 30,35 30,10 5,10" />
<rect width="35" height="80" id="base2" />
</defs>
<polygon transform="translate(0, 45)" points="5,5 80,5 80,40 5,40 5,35 75,35 75,10 5,10" style="fill: url(#three_stops_4); stroke: black;"/>
</svg>
That render like this:
Now those are the specs:
https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementGradientUnitsAttribute
What i do not understand is if:
1) the object bounding box transformation is applied to the gradient coordinates and then the gradient transform is applied.
OR
2) the gradientTransform is applied to the object bounding box and the the gradient coordinates are transformed.
The specs seems to say to take in the consideration the first option, but the point is that while the gradient should stretch from the whole width of the object, i clearly see it in the rendering that being rotated it stretches for the height of the object.
There is an additional stretching ( compression ) that seems to come from the object bounding box aspect ratio, applied after the rotation.
Does anyone know how exactly the renderer should behave?
It says in the spec (under gradientTransform):
This additional transformation matrix is post-multiplied to (i.e., inserted to the right of) any previously defined transformations, including the implicit transformation necessary to convert from object bounding box units to user space.
So depending on how you like to think about matrix multiplication, both answers are correct. In my way of thinking about it (transforms are applied right to left) #2 is correct. The gradient transform is applied first. Then the objectBoundingBox transform is applied. In pseudo code:
ctx.transform(gradientTransform)
ctx.transform(objectBoundingBoxTransform)
But it also depends on what 2D rendering library you are using, and whether you are using pre- or post- multiplication. Some rendering libraries let you set a transform matrix on the gradient object. For example, in Android you can do:
LinearGradient grad = new LinearGradient(...);
grad.setLocalMatrix(gradientTransformMatrix);
The accepted answer is the one that guided me to the solution.
The problem was translating some SVG peculiarities to JS canvas rendering.
That is hard and sometimes not possible at all with simple gradients.
For gradients that get applied to a filled shape, and that are boundingbox units and also have a gradientTrasnfrom attribute, you should:
var t = myGradientTransform;
MakeAPathForMyShape(ctx);
ctx.transform(width, 0, 0, height, minX, minY);
ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5], t[6]);
ctx.fill();
This works most of the time if you are using ctx.fill, does not work if you are using fillRect or fillText, because you cannot put transformations in the middle of the path traced and the fill operation.
Now let's talk of the stroke.
If you use the code above with stroke, you are going to apply the gradient transform to your strokeWidth, thing that maybe you do not want to.
If the scaling is not uniform between X and Y, you cannot compensate with changing ctx.strokeWidth;
A solution i had to fallback to, is to create a pattern and apply that.
So i calculate a bounding box for my shape, and i create a canvas with the same size.
Then make a path as big as the canvas, using its perimeter, and i use the above code on that context.
At this point i have a rect, as big as my shape, with the transformed gradient applied over.
Now i create a pattern from it:
ctx.strokeStyle = ctx.createPattern(tmpCanvas, 'no-repeat');
ctx.stroke();
in this way i can apply the transform to the gradient and not to the stroke size.
In the research i also evaluated the idea of transforming the normal of the gradient and find back again the points that would generate a transformed gradient in a non transformed space. This can work for linear gradients, but not for radial gradients, that get an ellipse shape and do need a classic trasnform to work.

How does fabric.Group positioning work?

I'm working on a project that requires groups of objects. I want to add something to a group after the group has already been drawn on the canvas. I put the gist of what I want to accomplish in the following jsfiddle: http://jsfiddle.net/85x3hzx7/2/
First off, I draw a grid in order to help visualize the position. I added the following line in order to have an origin that starts at the top left:
fabric.Object.prototype.originX = true; fabric.Object.prototype.originY = true;
Which is a line suggested by the creator of the library in order to use inheritance to set the origin to the top left for each object (see: Canvas coordinates in Fabric.js have offset). Leaving out this line of code gives a funky result. In the jsfiddle I added multiple blocks of code in order to try and accomplish my goal:
In code block 1, I add a Rectangle in a group and position that group to (100,100). This works nicely as expected:
In code block 2, I define a Circle and add it to the group. This results in some funky positioning and changing of the dimensions of the group:
Setting the circle to position (0,0) places it in what I think is the center of the previous bounding box, which doesn't make much sense to me since my origins are defined as top/left:
How do I position the circle so that it is in the top left corner of the rectangle AFTER having already created the group? There might be a bug at play here, or perhaps I'm not grasping the concept of positioning inside groups, and what adding something to a group does to the position/dimensions.
If you change the top and left coordinates of the circle as described below, it will position itself at the coordinates you desire.
var circle = new fabric.Circle({
radius: 10,
fill: 'red',
originX: 'left',
originY: 'top',
left:group.left-(group.width/2),
top:group.top-(group.height/2)
});
I am not too sure why I have to subtract half of group width and height from group's left and top. Here's your fiddle edited: http://jsfiddle.net/85x3hzx7/5/
Although, if you use the latest version of Fabric.js, you just have to set the left and top coordinates of new object to be added, to group's left and top coordinates. You can take a look at your code with latest version here: http://jsfiddle.net/rpko8z0r/3/
Please note that in new Fabric.js versions, default origin is set to top-left. So you don't need to set every object's origin to top-left, also following line of code is not needed.
fabric.Object.prototype.originX = true;
fabric.Object.prototype.originY = true;

How do I rotate or scale (transform) an SVG path relative to its center point?

I'm trying to rotate and scale shapes within an SVG around their center point. I've looked into several libraries, including Jquery, Greensock, D3, RaphaelJS, but I haven't been able to find any that provide a straightforward way to accomplish this. Each animates the shape from the origin point (which I understand is the default). I want to be able to spin a shape around its center point or scale it up or down from the center point.
Here are a couple examples using Greensock and D3 that illustrate the default behavior: http://jsbin.com/AHEXiPa/1/edit?html,js,output
Each of these examples bounce in and out from the top left as opposed to remaining stationary and expanding from the center of the triangle out in all directions.
Can one of the libraries I mentioned accomplish this, or is there another library or method I should consider?
Ideally, I need to be able to apply the animation/transform to an existing object in the DOM. D3 is good at this for instance, but Raphael seems to require converting an SVG to Raphael first prior to injecting it into the DOM.
Really its a case of pick the library that suits your needs, and then you will figure a way. As BigBadaboom says, if you do a search, there are lots of solutions.
To try and combine your questions, as sometimes the tricky bit is using an existing DOM object, I've included an example in Snap.svg. You can often do something similar in most libraries.
jsfiddle here Fiddle using your existing html.
s = Snap("#mySVGContainer1"); // create a canvas from existing svg
var triangle1 = s.select("#myShape1").transform("r90"); //select&transform existing object
p = Snap("#mySVGContainer2");
var triangle2 = p.select("#myShape2");
var bbox = triangle2.getBBox(); //bounding box, centre cx/cy
//rotate and scale with transform string (raphael/snap format)
triangle2.animate({ transform: "r180," + bbox.cx + ',' + bbox.cy + "s3,3," + bbox.cx + "," + bbox.cy }, 2000);
For rotations, as #Ian points out, you can specify the center of rotation. For other transformations, changes are defined relative to the path's (0,0) point.
The easiest way to get transformations to work relative to the path's center is to either:
Define the path so that it is centered around the (0,0) point; or
Wrap the path in a <g> element, and then translate it so it is centered on the (0,0) point of the <g> element's coordinate system.
Then, you can apply rotations, scales and transforms (on the <g> element, if using) and they will all be nicely centred.
The trickiest part is figuring out the "center" of an arbitrary shape. #Ian's approach of using the center of the bounding box will usually give decent results. If your shape is a polygon there are d3 functions you could use.
Example showing a shape moving with the mouse, rotating and changing scale, all centered around the center of the bounding box:
http://fiddle.jshell.net/LgfE3/
Edit: simplier jsfiddle
I've been looking for a long time, and will settle for the following.
1. Design your svg shape at coordinate x:0,y:0.
2. Identify by hand the center of rotation, by example, center = [ x:50,y:100].
3. Build a spinIt() function such :
function spinIt() {
needle.transition()
.duration(2000)
.attrTween("transform", tween);
function tween() {
return d3.interpolateString("rotate(-180, 50, 100)", "rotate(90, 50, 100)");
}
}
4. Use it on a triger:
svg.on("click", spinIt);
http://jsfiddle.net/SHF2M/79/

SVG Fill and Transform

I understand that fill is applied after transform, but I don't understand why. I've looked in the spec and cannot find information about why this might be.
I'd like to know if there is a way to have the fill applied to a group of elements, some using transform: rotate in a consistent manner. To illustrate the problem see the example code below:
<svg width='38' height='18' viewBox='0 0 38 18' xmlns='http://www.w3.org/2000/svg'>
<defs>
<linearGradient id='gradientFill' x2='0' y2='100%' gradientUnits='userSpaceOnUse'>
<stop stop-color='hsl(0, 0%, 0%)' />
<stop offset='100%' stop-color='hsl(0, 0%, 100%)' />
</linearGradient>
<path id='shape' d='M0,0m9,0c-3.92,0,-7.24,2.51,-8.48,6h3.29c0.83,-1.92,2.8,-3.56,5.19,-3c2.52,0.59,3,3.55,3,6c0,3.6,-0.6,6,-3,6s-3,-2.4,-3,-6h-6c0,4.97,4.03,9,9,9c4.97,0,9,-4.03,9,-9s-4.03,-9,-9,-9z'/>
<g id='test'>
<use xlink:href='#shape'></use>
<use transform='translate(20,0) rotate(180,9,9)' xlink:href='#shape'></use>
</g>
</defs>
<use class='logo' xlink:href='#test' fill="url(#gradientFill)"></use>
</svg>
As you see, on the non-rotated element the gradient runs top to bottom, as expected. However on the rotated element the gradient is also rotated so that it runs from bottom to top. This seems inconsistent to me, as the fill is applied to a group and not the individual objects.
My ultimate desire is to have the linear gradient flow from top to bottom on both shapes, but I'd like to continue using the use and transform method, if possible.
Can someone point me in the right direction in regards to the spec, and also suggest a solution to accomplish my goal?
Attributes like "fill" are not commands that operate on an object, they are properties that are used by the object when rendering and are inherited by an object's children. Just the same as CSS rules.
To achieve the effect you want, use Michael's solution, or create a second gradient that has it's x,y and x2,y2 flipped to match the 180 degree rotation.
That you apply transforms first is stated in the SVG specification for gradients when it talks about gradientUnits...
If gradientUnits="userSpaceOnUse", ‘x1’, ‘y1’, ‘x2’ and ‘y2’ represent values in the coordinate system that results from taking the current user coordinate system in place at the time when the gradient element is referenced (i.e., the user coordinate system for the element referencing the gradient element via a ‘fill’ or ‘stroke’ property) and then applying the transform specified by attribute ‘gradientTransform’.
If you apply a transform to the element it changes the element's co-ordinate system and that therefore affects the gradient too.
You should define your shape as a clip-path and then apply it to a rect that is filled with the gradient you want. You could also do it with a filter, although that's a little more complicated.

Resources